WireGuard VPN on pfSense: Secure Remote Access to a Kubernetes Cluster, Step by Step
Many infrastructures start with a few ports opened "just for now": an RDP to the support VM, an SSH to the application server, a port for the Kubernetes API. The temporary becomes permanent, and the network edge turns into a list of exceptions that nobody reviews.
In this article, we show, with a real anonymized case, how to replace this model with a WireGuard VPN on pfSense, connect a Linux client and grant access to a staging Kubernetes cluster. We also show the troubleshooting that appeared along the way. Real problems teach more than the ideal script.
The Scenario
-
Firewall: pfSense CE 2.8.1, running as a VM, with public IP directly on the WAN.
-
Internal network:
10.20.0.0/16, gateway10.20.0.1(pfSense itself). -
Access destination: a staging Kubernetes cluster (3 control-planes, 3 workers) and an edge Nginx that publishes the portal (
443) and the Kubernetes API (6443). -
Client: a Linux desktop (Linux Mint Debian Edition, based on Debian 13) in another city, behind a regular home router.
-
Previous situation: access via port forwards on the WAN (RDP and SSH open to any source) and through a web remote access gateway.
Objective: access the internal network and the cluster as if the client were on-site, without exposing administrative services on the internet.
Topology
flowchart LR
subgraph Remoto["Cliente remoto"]
C["Desktop Linux<br/>LAN 192.168.1.0/24<br/>wg0: 10.200.0.2/32"]
end
subgraph Site["Site principal"]
FW["pfSense<br/>WAN: 203.0.113.10<br/>LAN: 10.20.0.1/16<br/>tun_wg0: 10.200.0.1/24"]
NG["nginx-proxy<br/>10.20.0.10<br/>:443 portal · :6443 API"]
M["k8s-master-01..03<br/>10.20.0.21-23"]
W["k8s-worker-01..03<br/>10.20.0.31-33"]
end
C -- "UDP 51820<br/>(WireGuard)" --> FW
FW --> NG
FW --> M
FW --> W
Golden rule of addressing: the tunnel network must not collide with the firewall's LAN or with the network the client connects from. Ranges like
10.200.0.0/24usually fall outside the most common home and corporate networks.
Why WireGuard (and When to Prefer OpenVPN)
pfSense offers OpenVPN, IPsec and WireGuard. For this case, with one operator and few devices, WireGuard won by a wide margin:
Summary: WireGuard for administrators, few devices and site-to-site. OpenVPN when there are many users, MFA or centralized authentication requirements, or clients on networks that block UDP.
Part 1: Configuring pfSense
1.1 Installing the Package
System → Package Manager → Available Packages, search for wireguard and click Install → Confirm. Wait for the log to end with Success.
1.2 Creating the Tunnel
VPN → WireGuard → Tunnels → Add Tunnel
Save Tunnel → Apply Changes.
Assigned tunnel or not? For simple remote access, it is not necessary to assign the tunnel to an interface. The address stays on the tunnel itself and the rules go in the firewall's WireGuard tab. Assigning the interface is only necessary in scenarios with gateway, policy routing or per-interface NAT.
1.3 Enabling the Service
VPN → WireGuard → Settings → ☑ Enable WireGuard → Save → Apply Changes. Keep Hide Secrets enabled so that private keys are not visible in the interface.
1.4 Registering the Client (Peer)
For this step, you need the client public key, generated in Part 2. In practice, generate the keys on the client and come back here.
VPN → WireGuard → Peers → Add Peer
The Allowed IPs field on the server side has a dual function: it filters which source IPs are accepted from that peer and defines to which peer traffic destined to that IP is routed. Use a unique /32 per client.
1.5 Firewall Rules
Rule 1: allow WireGuard on WAN (Firewall → Rules → WAN → Add)
Rule 2: allow traffic from the tunnel (Firewall → Rules → WireGuard → Add)
Without the second rule, the tunnel establishes the handshake normally, but all traffic to the LAN is blocked. This is one of the most common mistakes.
Part 2: Configuring the Linux Client
2.1 Installation
sudo apt update
sudo apt install wireguard wireguard-tools resolvconf -y
The resolvconf allows wg-quick to apply the DNS = directive from the configuration.
2.2 Generating Keys on the Client Itself
sudo -i
cd /etc/wireguard
umask 077
wg genkey | tee cliente.key | wg pubkey > cliente.pub
cat cliente.pub # chave PÚBLICA → cadastre no peer do pfSense
Security best practice: generate the private key on the device that will use it. It should never travel via email, chat, ticket or the firewall interface. The
umask 077ensures that only root can read the files.
2.3 Configuration File
The command below creates /etc/wireguard/wg0.conf and injects the private key directly from the file, without copy-paste:
cat > /etc/wireguard/wg0.conf <<EOF
[Interface]
PrivateKey = $(cat /etc/wireguard/cliente.key)
Address = 10.200.0.2/32
DNS = 10.20.0.1
[Peer]
PublicKey = <CHAVE_PUBLICA_DO_SERVIDOR>
Endpoint = 203.0.113.10:51820
AllowedIPs = 10.200.0.0/24, 10.20.0.0/16
PersistentKeepalive = 25
EOF
chmod 600 /etc/wireguard/wg0.conf
2.4 Bring Up and Validate
wg-quick up wg0
wg show
The expected result is this:
peer: <CHAVE_PUBLICA_DO_SERVIDOR>
endpoint: 203.0.113.10:51820
allowed ips: 10.200.0.0/24, 10.20.0.0/16
latest handshake: 20 seconds ago
transfer: 9.23 KiB received, 7.54 KiB sent
persistent keepalive: every 25 seconds
The two indicators that matter:
-
latest handshakepresent means the tunnel has been negotiated; -
received> 0 means the server is responding.
ping -c 4 10.200.0.1 # pfSense pelo túnel
ping -c 4 10.20.0.1 # pfSense pela LAN
In our case, latency remained stable at around 29 ms between the two cities, with 0% packet loss.
Part 3: What Went Wrong (and How We Fixed It)
A script without problems teaches little. These were the four real stumbling blocks of this deployment, in order.
Problem 1: No Handshake, 0 B received
The tunnel came up on the client, but wg show did not show latest handshake and pings failed.
Cause: the endpoint had a wrong digit in the public IP. It had been transcribed from a low-resolution screenshot, and packets were going to another address.
Fix:
sed -i 's/<IP_ERRADO>/203.0.113.10/' /etc/wireguard/wg0.conf
wg-quick down wg0 && wg-quick up wg0
Lesson: copy the WAN IP as text, in Status → Interfaces on pfSense. Never transcribe from an image. WireGuard does not give an error when the destination is wrong: it simply does not receive a response.
Problem 2: "Am I Testing from Inside the Network?"
Before blaming the firewall, confirm where the client is connecting from. Testing a VPN from inside the LAN it protects generates false negatives, because of hairpin NAT and route conflicts.
ip -4 -br addr # IP local do cliente
curl -4 -s ifconfig.me # IP público de saída
If the local IP is on the remote LAN or the public IP is that of the firewall itself, the test is invalid. Use another network, such as 4G from a cell phone, for example.
Problem 3: Port 443 Works, Port 6443 Gives No route to host
With the VPN active, the portal on the edge Nginx responded, but the Kubernetes API, on the same host, returned No route to host immediately.
Diagnosis: if a port on the host responds, the route is correct. An instant No route to host is the signature of a local firewall (firewalld, by default) rejecting with icmp-host-prohibited. The hosts accepted the LAN (10.20.0.0/16), but not the VPN source (10.200.0.0/24).
There were two paths:
For a staging environment, we chose NAT.
Firewall → NAT → Outbound
-
Change the mode to Hybrid Outbound NAT → Save.
-
Add a mapping:
-
Save → Apply Changes and clear old states in Diagnostics → Command Prompt:
pfctl -k 10.200.0.2
Result: nc -vz 10.20.0.10 6443 started returning succeeded.
Common mistakes on this screen: leaving Address Family as IPv4+IPv6, choosing WAN address instead of LAN address in the translation (hosts would see the public IP), or typing the description in the Port or Range field.
For production: prefer to allow the VPN source on the hosts, for example
firewall-cmd --permanent --zone=trusted --add-source=10.200.0.0/24. This preserves traceability of who accessed what.
Problem 4: Cluster Nodes Do Not Respond
Even with NAT, ping and SSH to the masters failed. Before touching anything else, we isolated the problem by testing from pfSense itself:
ping -c 2 -S 10.20.0.1 10.20.0.21; nc -vz -w 3 10.20.0.21 22
pfSense also could not reach the node. So the problem was not the VPN. The cause was the simplest possible: the cluster VMs were powered off.
Lesson: before adjusting the tunnel, check if the firewall itself can reach the destination. If even it cannot, the VPN is out of the equation.
Decision Tree
Sem handshake?
├─ Endpoint e porta corretos?
├─ Chaves públicas trocadas corretamente (cliente ↔ servidor)?
├─ Regra UDP 51820 na WAN, acima de bloqueios?
└─ Diagnostics → Packet Capture (WAN, UDP 51820) mostra pacotes?
├─ Não → IP errado, CGNAT, roteador da operadora na frente, UDP bloqueado na origem
└─ Sim → chaves ou serviço WireGuard desabilitado
Handshake ok, mas sem acesso?
├─ Regra na aba WireGuard liberando a rede do túnel?
├─ AllowedIPs do cliente inclui a rede de destino?
├─ O próprio pfSense alcança o destino?
│ └─ Não → destino desligado, outra VLAN/bridge, firewall do host
└─ Sim → firewall do host filtrando a origem → Outbound NAT ou liberar a origem no host
Part 4: Accessing Kubernetes via VPN
With the tunnel active and the nodes powered on:
mkdir -p ~/.kube
scp [email protected]:/etc/kubernetes/admin.conf ~/.kube/config-homolog
export KUBECONFIG=~/.kube/config-homolog
kubectl get nodes -o wide
If you see x509: certificate is valid for ..., the address in server: is not in the apiserver certificate's SANs. Point to a master's IP or include the load balancer IP in the certificate's SANs.
Results
An important WireGuard detail for those concerned with security: it does not respond to packets that are not authenticated with a known key. To an internet scanner, port 51820 appears closed. This is a real reduction in attack surface compared to exposed RDP or SSH.
Governance: What Cannot Be Missing
Configuring the VPN is the easy part. For it to remain secure a year from now:
-
Close what the VPN replaced. Validate equivalent access through the tunnel and then disable old port forwards. VPN plus open ports is just one more open port.
-
Peer inventory. Maintain a table with tunnel IP, device, owner and date. A peer without an owner is a peer to revoke.
-
One key per device, generated on the device. Never reuse keys between machines or transmit private keys.
-
Least privilege. Replace the Destination: Any of the tunnel rule with aliases containing the networks and ports actually needed.
-
Traceability. In production, avoid NAT between VPN and LAN, so that server logs identify the client. This weighs in audits and LGPD requirements.
-
Pre-shared key. Adding a PSK per peer is simple and adds an extra layer of protection, including against future cryptographic threats.
-
DDNS. If the WAN has a dynamic IP, use a hostname in the
Endpointso the VPN doesn't stop when the ISP changes the IP. -
Configuration backup. After any change: Diagnostics → Backup & Restore.
Quick Reference
pfSense
Pacote ............ WireGuard
Túnel ............. tun_wg0 · 51820/UDP · 10.200.0.1/24
Peer .............. admin-desktop · dynamic · 10.200.0.2/32
Regra WAN ......... Pass UDP any → WAN address:51820
Regra WireGuard ... Pass any 10.200.0.0/24 → (redes necessárias)
Outbound NAT ...... Hybrid · LAN · 10.200.0.0/24 → 10.20.0.0/16 → LAN address (opcional, homologação)
Linux Client
wg-quick up wg0 # conectar
wg-quick down wg0 # desconectar
wg show # status / handshake
systemctl enable --now wg-quick@wg0 # subir no boot
Need Support with Your Infrastructure?
Nyverra Tecnologia works with infrastructure, edge security, virtualization and monitoring for companies in the healthcare, industry and services sectors. We do everything from reviewing internet exposure to designing auditable remote access. Talk to us via [email protected].
Keep reading
Read also

StorCLI - Complete Cheat Sheet
StorCLI is the official and modern tool for managing Broadcom/LSI RAID controllers. This guide covers essential commands, syntax and use cases.
12 min de leitura
Portal ITSMBusiness Services in Nyverra Portal: Translating IT into Business
The business doesn't feel a server go down — it feels email stop. The Business Services (Service Mapping) module of Nyverra ITSM Portal connects services like Email, ERP or PACS to CMDB assets, revealing the real impact of each failure.
5 min de leitura
SegurançaRPO vs RTO: How to Define Before Buying Backup (and Why Most Companies Get It Wrong)
Most companies buy backup asking the wrong question: "how much does it cost?". The right question is: how much data can I lose (RPO) and how long can I be down (RTO). Discover how to define these numbers per system and avoid purchasing protection based on price, not on real need.
9 min de leitura
Comments
No comments yet
Be the first to share your thoughts on this article.

Leave a comment