Reach Cockpit safely over WireGuard, not the internet
Put the WireGuard interface in its own firewalld zone, allow Cockpit only there, and prove that the internet cannot reach it.
Part 4 of 5 in Cockpit on openSUSE. Last updated
In short: Never forward port 9090: Cockpit's login is your system password. Forward only WireGuard's UDP port, put the wg0 interface in its own firewalld zone (for example internal), allow the cockpit service in that zone and remove it from public. Optionally bind cockpit.socket to the tunnel address with a drop-in (ListenStream= plus FreeBind=yes). Then browse to https://10.8.0.1:9090 with the tunnel up, and check that it times out with the tunnel down.
Why not just forward port 9090?
Cockpit logs in with your system accounts and, once you switch on administrative access, can do everything root can. On the open internet that login page gets password guessing within hours, and any future Cockpit bug is exploitable by anyone. A WireGuard tunnel turns this around: the router forwards one UDP port, and without a valid key nothing on the server even answers.
The plan
| What | Value |
|---|---|
| Tunnel network | 10.8.0.0/24 |
| Server in the tunnel | 10.8.0.1 on interface wg0 |
| Forwarded on the router | UDP 51820 only |
| Cockpit | https://10.8.0.1:9090, tunnel only |
WireGuard on the server
Install the tools, write /etc/wireguard/wg0.conf and start the tunnel (all as
root). The WireGuard config generator
creates the server and client configs with keys, in your browser:
sudo zypper install wireguard-tools
sudoedit /etc/wireguard/wg0.conf # paste the server config
sudo chmod 600 /etc/wireguard/wg0.conf # it holds the private key
sudo systemctl enable --now wg-quick@wg0
sudo wg show # peers and last handshake
Keys are plain files. Made in a scratch folder with umask 077, so even the public
keys are private to you (they do not need to be, but it does no harm):
$ umask 077; wg genkey > server.key; wg pubkey < server.key > server.pub
$ wg genkey | tee phone.key | wg pubkey > phone.pub; ls -l
total 16
-rw------- 1 user user 45 Sep 28 06:40 phone.key
-rw------- 1 user user 45 Sep 28 06:40 phone.pub
-rw------- 1 user user 45 Sep 28 06:40 server.key
-rw------- 1 user user 45 Sep 28 06:40 server.pub Real output on openSUSE Tumbleweed, wireguard-tools v1.0.20260223, 2026-09-28; user and host names and addresses redacted.
A public key is derived from its private key, so you can always check that a pair belongs together, and wg refuses anything that is not a key:
$ wg pubkey < phone.key; cat phone.pub
oXtKsyjAE+mjS12dr5yz/2q9s0MRegdWTHQg6ikSfBE=
oXtKsyjAE+mjS12dr5yz/2q9s0MRegdWTHQg6ikSfBE=
$ echo "not a key" | wg pubkey; echo "exit=$?"
wg: Key is not the correct length or format
exit=1 Real output on openSUSE Tumbleweed, wireguard-tools v1.0.20260223, 2026-09-28; user and host names and addresses redacted.
These keys were made for this page and thrown away; never publish a key you use.
firewalld: Cockpit only in the tunnel zone
These commands need root. Read what each one does before you run it; on a server you reach over SSH, keep a console at hand in case a rule is wrong.
sudo firewall-cmd --permanent --zone=public --add-service=wireguard
sudo firewall-cmd --permanent --zone=internal --add-interface=wg0
sudo firewall-cmd --permanent --zone=internal --add-service=cockpit
sudo firewall-cmd --permanent --zone=public --remove-service=cockpit
sudo firewall-cmd --reload
sudo firewall-cmd --get-active-zones --zone=public --add-service=wireguard: accept the tunnel itself (UDP 51820) on the normal network card.--zone=internal --add-interface=wg0: everything that arrives through the tunnel is judged by theinternalzone.--zone=internal --add-service=cockpit: allow 9090/tcp there, and only there.--zone=public --remove-service=cockpit: close 9090 on the normal network card, in case it was opened earlier. firewalld only warns if it was not.--reloadapplies the permanent config;--get-active-zonesshould listwg0underinternal.
internal also allows a few services by default (such as ssh); check with
sudo firewall-cmd --zone=internal --list-all. Want Cockpit on the LAN as well?
Give the LAN subnet a zone with --add-source and allow cockpit there too, as in the
firewalld basics guide. The
firewalld port helper builds such rules with the
undo commands.
What the two firewalld names stand for, straight from the files firewalld ships. The
internal zone allows these services before you add cockpit:
$ cat /usr/lib/firewalld/services/wireguard.xml
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>WireGuard</short>
<description>WireGuard is the simple, fast and modern VPN. The port needs to be open if a peer has this host explicitly configured as endpoint.</description>
<port protocol="udp" port="51820"/>
</service>
$ grep "service name" /usr/lib/firewalld/zones/internal.xml
<service name="ssh"/>
<service name="mdns"/>
<service name="samba-client"/>
<service name="dhcpv6-client"/> Real output on openSUSE Tumbleweed, firewalld, 2026-09-28; user and host names and addresses redacted.
Optional: listen on the tunnel address only
The firewall is enough on its own. For a second layer, make Cockpit listen only on the tunnel address, with a socket drop-in (needs root):
sudo systemctl edit cockpit.socket [Socket]
ListenStream=
ListenStream=10.8.0.1:9090
FreeBind=yes sudo systemctl daemon-reload
sudo systemctl restart cockpit.socket
sudo ss -tln | grep 9090 # 10.8.0.1:9090 only
The empty ListenStream= clears the default "all addresses" entry.
FreeBind=yes lets the socket start at boot even before wg0 has its
address. This is the method the Cockpit
documentation describes.
Test it from outside
Use a phone on mobile data, so you really come from the internet:
- WireGuard on:
https://10.8.0.1:9090shows the Cockpit login. - WireGuard off: the same address times out, and so does your public IP on port 9090.
- On the server:
sudo wg showlists the phone's latest handshake.
If the page does not load with the tunnel on, check that the client's AllowedIPs
includes 10.8.0.1 (or 10.8.0.0/24), and that wg0 really
sits in the internal zone. SSH works the same way; the
SSH hardening guide explains why port 22 should not be
forwarded either.
Frequently asked questions
Can I put Cockpit behind a reverse proxy instead?
Yes, Cockpit documents proxy setups, but a proxy on the internet still exposes the login page to everyone. A VPN hides the service completely: without a valid WireGuard key, nothing on port 9090 answers.
My WireGuard connection is managed by NetworkManager. Does --add-interface still work?
NetworkManager assigns zones to the interfaces it manages, so set the zone on the connection instead: sudo nmcli connection modify wg0 connection.zone internal, then bring the connection up again.
The browser warns about the certificate every time. Can I fix that?
Put a certificate and key for the name you use into /etc/cockpit/ws-certs.d/ (as name.crt and name.key) and restart cockpit.socket. For a tunnel-only address, accepting the self-signed certificate once per browser is also fine.