Valheim Server with Reverse Proxy and VPN

I’m trying to setup external access to my Valheim server and it doesn’t seem to work via proxy, I wonder if anyone has any comments or similar experience.

What I have is:

  • VPS with external IP, running one instance of Nginx as a proxy [1], and a WireGuard host.
  • Home server, running another Nginx as proxy [2], and a WireGuard peer.
  • A bunch of docker containers with stuff.

This setup has been working great for a long time for HTTP(S) traffic, where the flow is:
A device connects → Nginx[1] accepts connections and forwards them into WireGuard tunnel → Nginx[2] accepts connections from the tunnel and forwards them to corresponding containers → services in containers do their thing.

Now I would like to add Valheim to the mix. I used lloesche/valheim-server, configured it, and exposed 2456 and 2457 ports (as 9100 and 9101 in the example below). It works fine if I connect to it directly (within LAN) - I can join the server, load into the world, etc. But I get “Failed to connect” message if I try to connect via external address.

In other words, this works:

(( LAN (Valheim) <=> (MyPC) ))

But this doesn’t:

(( LAN (Valheim) (MyPC) ))  ((VPS))
          ^        ^=========^   ^
          ^======================^

Which indicates a problem at proxy or VPN level, or some side-effect of Valheim network handling in this case.

Nginx[1] config is roughly this:

stream {
    server {
        listen PUBLIC_IP:42456 udp reuseport;
        proxy_pass WG_IP:12456;
    }
    server {
        listen PUBLIC_IP:42457 udp reuseport;
        proxy_pass WG_IP:12457;
    }
}

And Nginx[2] is:

stream {
    server {
        listen WG_IP:12456 udp reuseport;
        proxy_pass LAN_IP:9100;
        proxy_requests 999999;
    }
    server {
        listen WG_IP:12457 udp reuseport;
        proxy_pass LAN_IP:9101;
        proxy_requests 999999;
    }
}

VPS ports are opened via ufw: “42456/udp ALLOW IN Anywhere” etc.

I did manage to make netcat work, i.e. I run

$ nc -uvl LAN_IP 9100

on home server and

$ nc -uv PUBLIC_IP 42456

on my pc and it works mostly as expected (the output seems to be truncated to MTU size but I doubt that’s an issue).
So the whole reverse-vpn-proxy idea works in principle, just not in Valheim. Neither Valheim’s Player.log nor Nginx logs seem to have anything odd.

What I tried so far:

  • setting WireGuard’s MTU to 1200
  • omitting “proxy_requests”
  • omitting “reuseport”
  • using “proxy_protocol on;”
  • using “proxy_responses 0;”

What I will not use:

  • iptables
  • another VPN
  • port forwarding

I don’t know if you would like to go this route. I vaguely remember something about UDP and nginx not working for valheim but the details escape me. But I used tailscale (Im sure your wireguard setup is fine)
Instead of Nginx I used iptables. I checked my notes and they are super sloppy. But what you want to do it is allow ipv4 forwarding

$ sysctl -w net.ipv4.ip_forward=1

Then you can allow the ports for ufw

$ ufw allow 2458

Then you can create the redirection rule using Ip tables DEST_IP will be the ip wireguard assigns to your valheim server and the PUB_UDP_PORT should be 2456.

add_iptables_once nat PREROUTING -p udp --dport “2456”
-j DNAT --to-destination “100.100.100.100:2456”

My notes said this allows traffic to be able to return to source from your target server(valheim server)

add_iptables_once nat POSTROUTING -o “$TS_IF” -j MASQUERADE

Then finally the bread and butter

add_iptables_once filter FORWARD -p udp -d “$DEST_IP” --dport “$PUB_UDP_PORT”
-m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
add_iptables_once filter FORWARD -p udp -s “$DEST_IP” --sport “$PUB_UDP_PORT”
-m conntrack --ctstate ESTABLISHED -j ACCEPT

Im not sure what the option would be called in your wireguard config but in tailscale I can designate a connected device as an “exit node” Which allows traffic to be routes out of that device. Enabling that option for your (valheim) server might be needed.

I hope this helps!

Thanks, but I just don’t feel like learning iptables syntax for a one-time task, and then messing something up :sweat_smile:
I do use ipv4 forwarding - that was necessary for Nginx TCP proxy in my case.

Why doesn’t Valheim work with Nginx though? Is it because UDP is stateless and Valheim can’t maintain connection or packets can’t find their way back from server? Is it some fragmentation due to wrapping application’s UDP into WireGuard’s UDP?
The quest continues…

Workaround 1

Use only the VPN, without Nginx. VPS acts as publicly accessible VPN server, and game container together with all players are VPN peers.

Pros:

  • Simple setup, requiring only minimal VPN config. From Valheim point of view, everyone is in the same LAN which is a supported case.

Cons:

  • Every player must configure and enable VPN which is not convenient.

Example docker-compose configuration:

services:
  wg:
    image: linuxserver/wireguard:latest
    cap_add:
      - NET_ADMIN

  valheim:
    image: lloesche/valheim-server:latest
    depends_on: [wg]
    network_mode: service:wg

Note: some docker details are omitted from this example because they are specific to my case and are not necessary for illustrating the point.


Workaround 2

Use VPN with single Nginx[1] instance. In this scenario, Nginx accepts incoming connections and forwards them via VPN directly into Valheim container.

Pros:

  • Still relatively simple configuration, no need for any player to setup anything, everyone can simply connect to Valheim server via its public IP.

Cons:

  • Possibly lower security because there are fewer boundaries between Valheim container and internet-facing server as they are internally connected by VPN tunnel. However I can’t think of an actual attack vector even if Valheim happens to have bugs/exploits.

Example Nginx configuration:

stream {
    server {
        listen PUBLIC_IP:42456 udp;
        proxy_pass WG_IP:2456;
        proxy_timeout 30s;
    }
    server {
        listen PUBLIC_IP:42457 udp;
        proxy_pass WG_IP:2457;
        proxy_timeout 30s;
    }
}

proxy_timeout is not strictly necessary, I just think it will help the game to not hang forever when someone disconnects.
proxy_bind however prevents connection completely, probably because WireGuard interface expects only its internal IPs.
Valheim container config can be the same as in Workaround1 example.