Installing wireguard on CentOS Stream 9

As I do a lot of my research on new Domino versions, Connections versions and HCL DX on my own server at home and as I’m often not at home, I figured I needed a VPN tunnel to my server, so I can work as if I am home. Wireguard has become kind of the de facto standard for these kind of situations, so I looked into installing it on my CentOS Stream 9 host.

I’m rarely installing anything straight on my host these days. I prefer containers as it makes it easier to configure and more secure. In this case, Wireguard isn’t even in one of the default CentOS 9 repositories, so it makes even more sense to use a container. On Docker Hub, there are two container images that grabbed my attention. By far the most popular image for the Wireguard server comes from linuxserver.io. I like their images. I use them for a plethora of things, so that’s a logical candidate. The other image of interest comes from an individual, but it’s been downloaded over a million times and updated frequently. It’s a UI for Wireguard. If you’re going to use Wireguard for a single connection you don’t really need it, but if you want some form of VPN administration, I can see that it could be handy. The documentation for the linuxserver.io Wireguard image is quite good, but I needed to make some alterations to get it to work on CentOS 9 Stream, hence this article.

Let’s start with my docker compose file:

---
version: "2.1"
services:
  wireguard:
    image: lscr.io/linuxserver/wireguard:latest
    container_name: wireguard
    cap_add:
      - NET_ADMIN
      - NET_RAW
    environment:
      - PUID=977
      - PGID=977
      - TZ=Europe/Amsterdam
      - SERVERURL=zeus.martdj.nl
      - SERVERPORT=51820 #optional
      - PEERS=myLenovo,myDell,myMacbook,myiPhone,myiPad
      - PEERDNS=auto #optional
      - INTERNAL_SUBNET=10.13.13.0 #optional
      - ALLOWEDIPS=0.0.0.0/0 #optional
      - LOG_CONFS=true #optional
    volumes:
      - /local/wireguard/config:/config
    ports:
      - 51820:51820/udp
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
    restart: unless-stopped

The deviation from the docker compose file from the documentation is the NET_RAW cap_add. This one turned essential to get everything to work. However, with just this docker compose file, you will see the following message in the log:

[#] iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
iptables v1.8.9 (legacy): can't initialize iptables table `filter': Table does not exist (do you need to insmod?)
Perhaps iptables or your kernel needs to be upgraded.
[#] ip link delete dev wg0
[wireguard] | **** Tunnel /config/wg_confs/wg0.conf failed, will stop all others! ****
[wireguard] | **** All tunnels are now down. Please fix the tunnel config /config/wg_confs/wg0.conf and restart the container ****
[wireguard] | [ls.io-init] done.

To solve this, you need to add a module to your kernel on your host:

modprobe iptable_raw

This was the only one I needed. take notice, this depends on your kernel version. If your Linux kernel is pre-5.6, you’re going to need more! If you entered this command, but used the docker compose file from the documentation (so without the NET_RAW), you’re going to see the next message in the log.

[#] iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
iptables v1.8.9 (legacy): can't initialize iptables table `filter': Permission denied (you must be root)
Perhaps iptables or your kernel needs to be upgraded.
[#] ip link delete dev wg0
**** Tunnel /config/wg_confs/wg0.conf failed, will stop all others! ****
**** All tunnels are now down. Please fix the tunnel config /config/wg_confs/wg0.conf and restart the container ****
[ls.io-init] done.

That’s where the NET_RAW comes in to grant the container the necessary permissions to make the changes to iptables. When both are present, wireguard starts properly:

[#] iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
**** All tunnels are now active ****
[ls.io-init] done.

As I use Podman instead of Docker, I created a small systemd-service file for wireguard:

# container wireguard.service

[Unit]
Description=Podman container for wireguard
Documentation=https://github.com/linuxserver/docker-wireguard

[Service]
Type=simple
TimeoutStartSec=1m
ExecStartPre=/usr/bin/podman rm -i wireguard
ExecStartPre=/usr/sbin/modprobe iptable_raw
ExecStart=/usr/bin/podman-compose -f /local/wireguard/wireguard.yml up
ExecStop=-/usr/bin/podman-compose -f /local/wireguard/wireguard.yml down
ExecReload=-/usr/bin/podman-compose -f /local/wireguard/wireguard.yml down
ExecReload=-/usr/bin/podman-compose -f /local/wireguard/wireguard.yml up
Restart=always
RestartSec=30
KillMode=none

[Install]
WantedBy=multi-user.target

As you can see from my docker compose file, I don’t use the wireguard-ui. Personally, I found no need for it.

Resources

There were a couple of url’s that gave me the information I needed to get my setup to work.

On running Wireguard as a rootless container

On adding iptables_raw to the default loaded modules