Getting a static IP Address

Hey, in this Post I want to explain a bit how I finally moved away from my dynamic IP address from my ISP and got a static one. There are not that many advantages, a few that I encountered are:

  1. Dynamic IP addresses are sometimes blocked for some Users. I visited a family member recently and wanted to show them this website. But it seems like their home router has a setting which will block spam websites (which makes a lot of sense). Since these are mostly on dynamic IPs, a visit to jonnyebinger.de just sends them to 192.168.0.1. Not ideal
  2. Emails, even if you do everything right and do not mess up your settings, will often be considered spam, just because they come from a public IP. So, to setup a reliable Email-server, you need a static IP

Get a static IP

First step to a static IP, is getting one. I live in Europe, here netcup sometimes has discounts on old VPS (virtual private servers). Mine costs me about 3 euros per month, which is pretty good and about the same as the electrical bill for running a RaspberryPi 24/7. The specs for it are also about the same, too.

Specs of my VPS

Setup

If you have that, you somehow have to connect your home server to the server with that IP address. This is done using a VPN, directly into your network.
There are different options out there, most of them run on the wireguard protocol. This post is actually about how to setup this wireguard connection, because I felt there was very complicated tutorials out there. If you want to search for information, use anything with wg-easy. This is the protocol that is used by wireguard to configure the settings in your firewall and these posts will mostly be easy to understand.

Just to clarify a few things upfront, since I thought that was always confusing because they talk about server, client, peer and so on. Server is the device, taking the connections. In my case, my home-server, the computer running in my local network. The client is the VPS, the server I bought from netcup. Peer can be both devices depending on where you look, which sometimes makes it hard to follow (at least for me)

The path I chose was running Wireguard in a docker container on my home-server and normally as an application on my VPS. The docker container I used was wg-easy as I liked the concept of also having a graphical interface. Another upside of this container is, that you dont have to worry about keypairs and server/peer configurations, as all of this is taken care of by the container directly.

I chose to do it in a docker container on my host, because I wanted to make sure to keep the amount of possible connections to my local network as small as possible and security as high as possible. Meaning, since docker uses their own subnet of 172.x.x.x, the client (VPS) can not access the local network (or only the devices I tell it to) and I can directly use the IP-Addresses of my docker containers.
The compose file I configured for this, follows the structure of all my compose files, its just that this needs a lot of environment variables.

Home server

Compose file

The compose file is nothing fancy, just remember to forward the udp port in your router (I lost 2 days because I forgot it and troubleshooted the wrong stuff). I also put this container into my network of containers behind the reverse proxy. That way, it can access all containers, connected to this network using their docker IP address and I dont have to use another reverse proxy or something. We could also configure the VPS to use the docker DNS, then we could also use hostnames (meaning the direct names of the containers, just like caddy does)

services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy
    container_name: wg-easy
    env_file:
      - .env
    ports:
      - "51820:51820/udp"
      - "51821:51821/tcp"
    volumes:
      - /data/docker/wireguard:/etc/wireguard
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
    networks:
      - proxy-net
    restart: unless-stopped

networks:
  proxy-net:
    external: true

.env file

The environment variables are mostly taken from the template. Change the network controller to the one of your host-computer (in my case eth0) and the DNS to the IP of your home network. As this IP is the dynamic one, it doesnt make sense to hardcode it in here. I have setup one subdomain, which holds the dynamic IP and is still updated manually by an automation script.

LANG=en
PASSWORD=securepassword
# WG_DEFAULT_DNS=1.1.1.1
WG_DEFAULT_ADDRESS=10.0.0.x
WG_PORT=51820
WG_CONFIG_PORT=92820
PORT=51821
WG_ALLOWED_IPS=172.0.0.0/8, 10.0.0.0/24
WG_PERSISTENT_KEEPALIVE=25
WG_HOST=local.jonnyebinger.de
WG_DEVICE=eth0
WG_POST_UP="iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE; \
iptables -A FORWARD -i eth0 -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT; \
iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT"
WG_POST_DOWN="iptables -t nat -D POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE; \
iptables -D FORWARD -i eth0 -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT; \
iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT"

The cool thing about this solution is, you can now use the graphical interface available at local.ip:51821 to configure clients that can connect to your server.

Caution: If you want to use this as a VPN for your phone or laptop, you of course have to change the ip's that you can access with it, otherwise you cannot do anything with it.

With this you can start the docker container!

VPS

Of course first you have to install wireguard. sudo apt install wireguard should be enough.

On the home-server side, you can use the GUI to generate a new device and then download the configuration data for it.
VPS configuration

The configuration data for my VPS looks like this:

[Interface]
PrivateKey = auto-generated-by-wg-easy
Address = 10.0.0.2/24
# DNS = 192.168.0.2
[Peer]
PublicKey = auto-generated-by-wg-easy
PresharedKey = auto-generated-by-wg-easy
AllowedIPs = 172.0.0.0/8, 192.168.0.0/24, 10.0.0.0/24
PersistentKeepalive = 25
Endpoint = local.jonnyebinger.de:51820

When you download a config file from the GUI, it will add a DNS parameter. I found that, when enabling the wireguard interface afterwards, I would get an error like resolvconf: command not found.
To fix this, I had to install sudo apt install openresolv (source) and also remove the immutable flag from resolv.conf with sudo chattr -i /etc/resolv.conf (source) to get it working.

I have set the DNS to my PiHole at home, you can set it to 1.1.1.1 or whatever you prefer.

Now you can do wq-quick up wg0 and it should setup all IP rules necessary.
This is what I got as output afterwards.

[#] ip link delete dev wg0
[#] resolvconf -d wg0 -f
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.0.0.2/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] resolvconf -a wg0 -m 0 -x
[#] ip -4 route add 192.168.0.0/24 dev wg0
[#] ip -4 route add 172.0.0.0/8 dev wg0

Setup caddy

Last step is to install caddy on the VPS and run the traffic you want through that config.
This is the config for this blog for example:

jonnyebinger.de {
	reverse_proxy /* {
		to 172.19.0.4:2368
		import header1
	}
	header {
			import header2
		}
}

header1 and header2 are shortcuts to simplify the file a bit and make it more readable. They are just attached to the top of the caddyfile. You can also import them from a separate file.

(header1) {
  header_down -Strict-Transport-Security
  header_down -X-XSS-Protection
  header_down -X-Robots-Tag
  header_down -X-Download-Options
  header_down -X-Permitted-Cross-Domain-Policies
  header_down -X-Content-Type-Options
  header_down -X-Frame-Options
  header_down -Referrer-Policy
  header_down -X-Powered-By
  header_down -Server 
}

(header2) {
	# Enable HTTP Strict Transport Security (HSTS) to force clients to always
	# connect via HTTPS (do not use if only testing) for 1 year
	Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"
	# Expect-CT set to 24 hours
	Expect-CT "enforce, max-age=86400"
	# Enable cross-site filter (XSS) and tell browser to block detected attacks
	X-XSS-Protection "1; mode=block"
	# Prevent some browsers from MIME-sniffing a response away from the declared Content-Type
	X-Content-Type-Options "nosniff"
	# Disallow the site to be rendered within a frame (clickjacking protection)
	X-Frame-Options "SAMEORIGIN"
	# Remove Server Header
	Server ""
	X-Robots-Tag "noindex,nofollow"
	X-Download-Options "noopen"
	X-Permitted-Cross-Domain-Policies "none"
	Referrer-Policy "strict-origin-when-cross-origin"
	Permissions-Policy "accelerometer=(), autoplay=(self), camera=(self), encrypted-media=(self), fullscreen=(self), geolocation=(self), gyroscope=(), magnetometer=(), microphone=(self), display-capture=(self), midi=(), payment=(), picture-in-picture=*, sync-xhr=(), usb=(), interest-cohort=()" 
}

With this, the ip address to the outside world of your website should now be the static one.

For more information about the headers have a look at the mozilla observatory. There you can scan your website (or mine if you want) and see what headers you need to set and if everything is working as expected