====== Exposing a Local Server to the Public Internet Safely ====== These are the steps I took in order to securely expose a local server on my network to the wild west of the public internet, allowing me to free myself from cloud providers and properly self-host everything (with a small exception). In this log/tutorial you will notice that I don't talk about how to secure the network that a "semi public-facing" device is in. The reason for this is that everyone's network is different, and in my specific case, all devices that are going to be proxied to the outside world will be on a physically separate network (5G modem in my case), thus I don't have to worry about having a compromised device on my home's network. ===== Bridge VPN ===== In order to ensure that I don't have any open ports on my home router and no public internet traffic flowing through my home network, I've decided to use a VPN and a cloud VPS (the only exception to the self-hosting everything rule) to provide a secure tunnel for traffic to flow through. This was done using the amazing [[https://www.softether.org|SoftEther VPN project]], which apart from [[https://www.wireguard.com|Wireguard]] and [[https://tailscale.com|Tailscale]], is in my opinion the best VPN software in the market. ==== Setting up the Server ==== To set the VPN software up on a [[https://www.hetzner.com|Hetzner VPS]] in [[https://www.softether.org/4-docs/1-manual/3._SoftEther_VPN_Server_Manual/3.6_Local_Bridges|Local Bridge]] mode in order to avoid the bottleneck of [[https://www.softether.org/index.php?title=4-docs/1-manual/3._SoftEther_VPN_Server_Manual/3.7_Virtual_NAT_%26_Virtual_DHCP_Servers|SecureNAT]], I've followed the [[https://tweenpath.net/softether-vps-local-bridge/|following tutorial]]. Since Hetzner VPSes by default use DHCP for IPv4 address attribution, this will conflict with the DHCP server we need to install in order to run the local bridge, so following [[https://docs.hetzner.com/cloud/servers/static-configuration/|this Hetzner tutorial for static IP configuration]] is required in order for the setup to work. Since I wasn't able to get the DHCP server using ''dnsmasq'' to work no matter what I tried, I had to enable SecureNAT on the server, but disabling the Virtual NAT which is the extremely slow layer that greatly impacts performance, thus only using the DHCP server. The configuration used was the following: {{:log:softether-securenat-prefs.png?400|SecureNAT Configuration}} Since we are using a DHCP server to hand out IPs in our VPN we need to ensure that the server also has an IP assigned to the TAP interface that it's using to communicate within the VPN's network. This can be tested by running ''dhclient tap_vpn'', where ''tap_vpn'' is the VPN's TAP interface name, and later making this change permanent by adding the following to your ''/etc/network/interfaces'' file (assuming Debian): auto tap_vpn allow-hotplug tap_vpn iface tap_vpn inet dhcp The last step in the server side of things is to ensure that ''systemd'' can properly start the server upon a system start. Since all tutorials on the internet are still using ''initd'', here I have the ''systemd'' unit file that I used, which was taken from the [[https://packages.debian.org/bookworm/softether-vpnserver|softether-vpnserver]] Debian package and slightly modified: [Unit] Description=SoftEther VPN Server After=network.target auditd.service [Service] Type=forking TasksMax=16777216 ExecStart=/root/vpnserver/vpnserver start ExecStop=/root/vpnserver/vpnserver stop KillMode=process Restart=on-failure CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SYS_NICE CAP_SYSLOG CAP_SETUID [Install] WantedBy=multi-user.target After all of these changes a reboot should be done on the server just to ensure that everything comes up automatically and works. ==== Securing the VPN Network ==== Since there will be different types of users on this VPN, it's important to create some groups, at least one for your public-facing servers and another one for all your local servers. This will make ACLs much easier to create and manage. Also ensure that your servers are all authenticating with extremely strong passwords or client certificates. It's also important to ensure that, if a system on your VPN's network becomes compromised, that an attacker isn't able to use it to jump to other nodes on your VPN, specially the internet facing server. For this, a simple but effective measure would be to setup some [[https://www.softether.org/4-docs/1-manual/3._SoftEther_VPN_Server_Manual/3.5_Virtual_Hub_Security_Features#3.5.10_Packet_Filtering_with_the_Access_List|Access List]] rules blocking traffic that could be used to compromise other systems. ===== Reverse Proxy ===== Since we are not exposing our local network directly to the open internet by means of port forwarding, we will require a reverse proxy server that is exposed to the internet. In this case I have a Hetzner VPS running [[https://www.debian.org|Debian]] and [[https://nginx.org/en/|nginx]], this is the simplest and most flexible setup for a reverse proxy. Even though I have many years of experience with [[https://httpd.apache.org|Apache]] and have been a bit reluctant to move to nginx, this is the perfect use for it, after all [[https://blog.nginx.org/blog/celebrating-20-years-of-nginx|it was initially developed as a reverse proxy]]. It's super performant as a reverse proxy, easy to configure, integrates well with [[https://certbot.eff.org|certbot]], and can [[https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html|proxy raw TCP/UDP streams]]. ==== Setup for HTTP(S) ==== Setting up nginx for HTTP(S) traffic is super simple, all we need is nginx itself and certbot installed, after these are installed it's a simple matter of configuring virtual hosts for each site we wish to serve. For each site you want to configure you need to create a new file under ''/etc/nginx/sites-available/'' with the following content: server { listen 80; listen [::]:80; server_name domain.tld www.domain.tld; location / { proxy_pass http://ip_vpn_local:80; include proxy_params; } } To enable this virtual host we need to symlink it to the ''/etc/nginx/sites-enabled/'' and restart the web server: ln -sf /etc/nginx/sites-available/yourdomain /etc/nginx/sites-enabled/ systemctl restart nginx This is all it takes to reverse proxy an HTTP website. Now for enabling HTTPS so that modern, pedantic and useless, browsers can access our website without complaining: apt install certbot python3-certbot-nginx certbot --nginx --no-redirect systemctl reload nginx Follow the wizard for certbot and you should now have a website that has HTTPS active and won't automatically redirect to it, so that older browsers are still able to access your website without requiring useless encryption. ==== Setup for TCP ==== Setting up nginx for proxying TCP streams is even easier than for HTTP. This is useful for things like [[https://en.wikipedia.org/wiki/Gopher_(protocol)|Gopher]]. To set this up you need to install the stream module for nginx: apt install libnginx-mod-stream Next you should edit your ''/etc/nginx/nginx.conf'' and append the following at the end of the file: stream { server { listen 70; listen [::]:70; proxy_pass ip_vpn_local:70; } } It's important to note that this needs to go in your ''/etc/nginx/nginx.conf'' below the ''http {} '' directive and that, as usual, a restart of the web server is required.