DIY Debian Router - Part 9: Port forwarding with nftables
Introduction
This is Part 9 of the DIY Debian Router series. See Part 1 for the series introduction and links to all parts.
Port forwarding enables external Internet access to services hosted on the LAN by translating incoming WAN traffic to internal addresses. This allows hosting services on our residential Internet connections, making them accessible from anywhere on the Internet.
This part covers:
- Port forwarding implementation via nftables DNAT
- Firewall rules for inbound service access
- Security considerations for exposed services
- IPv6 considerations
- Testing and troubleshooting
Port forwarding architecture
Port forwarding uses Destination Network Address Translation (DNAT) to rewrite packet destinations:
- External client sends packet to
[WAN IP]:443 - Router (DNAT) rewrites destination to
192.168.0.10:443 - Router (forward chain) permits traffic to internal server
- Internal server receives packet, responds
- Router (SNAT/masquerading) rewrites source back to WAN IP
- External client receives response
This is bidirectional NAT: DNAT for inbound, SNAT for outbound.
Security considerations before exposing services
Exposing services to the Internet introduces significant risk. Every forwarded port is a potential attack vector. Before enabling port forwarding:
- Ensure service is hardened: Apply all security updates, disable unnecessary features
- Use strong authentication: SSH keys (no passwords), complex credentials for web services, etc
- Implement rate limiting: Fail2ban, application-level throttling
- Monitor logs: Automated alerting for suspicious activity
- Minimize exposed services: Only forward what is absolutely necessary
Recommendation: Use port forwarding only when VPN (covered in Part 8) or reverse proxy solutions are unsuitable.
nftables port forwarding configuration
Port forwarding rules are added to the ip nat table's prerouting chain (for DNAT) and the inet filter table's forward chain (for stateful filtering).
Edit /etc/nftables.conf (configured in Part 6):
Step 1: Add DNAT rules, locate the table ip nat section and modify the prerouting chain:
Port translation example: External port 2222 → internal port 22 allows SSH access without exposing standard port 22 (reduces automated attack traffic).
Step 2: Add forward chain rules, permitting translated traffic in the forward chain:
Important: Forward chain rules must match the post-DNAT destination (internal IP and port, not external).
Complete example configuration snippet
################## NAT table (IPv4 only) ##################
Applying configuration changes
After editing /etc/nftables.conf, apply changes:
Verify rules loaded correctly:
Output should include DNAT rules in the prerouting chain and corresponding accept rules in the forward chain.
IPv6 port forwarding
IPv6 does not use NAT; traffic routes directly to clients' globally routable addresses, so "port forwarding" is simply firewall rule creation.
To expose an IPv6 service, add a forward chain rule:
# In forward chain, for IPv6 web server on fd09:dead:beef::10
⚠️ IPv6 clients are directly accessible from the Internet (no NAT hiding). Firewall rules are critical.
Testing port forwarding
Internal testing (same network)
From a LAN client, test access to the internal service:
Web server should respond (confirms service is running).
External testing
From an external host (mobile phone on cellular, VPS, friend's network):
Same web server response is expected (confirms port forwarding works).
Connection tracking
On the router, monitor connection tracking during external access:
|
In another terminal, trigger external access. Expected output:
[NEW] tcp 6 120 SYN_SENT src=X.X.X.X dst=Y.Y.Y.Y sport=54321 dport=80 ...
[UPDATE] tcp 6 60 SYN_RECV src=192.168.0.10 dst=X.X.X.X sport=80 dport=54321 ...
[UPDATE] tcp 6 432000 ESTABLISHED src=192.168.0.10 dst=X.X.X.X sport=80 dport=54321 ...
This shows the connection being tracked and translated correctly.
Firewall log inspection
If traffic is blocked, enable logging temporarily:
# In forward chain, add before existing rules:
Reload firewall:
Monitor logs:
|
Trigger external connection and observe log entries.
Security hardening for exposed services
Rate limiting with nftables
Limit connection rate to prevent brute-force attacks:
# In forward chain, before port forwarding rules:
Connections exceeding 100 per minute are dropped.
Application-level security
- SSH: Disable password authentication (
PasswordAuthentication no), use key-based auth - Web servers: Enable HTTPS with Let's Encrypt certificates, strong TLS configuration
- Minimize exposed functionality: Disable admin interfaces, unnecessary endpoints, etc
Next Steps
Port forwarding exposes services to the Internet but requires a consistent way to reach the router. Residential ISPs typically assign dynamic IP addresses that change periodically. Part 10 covers Dynamic DNS configuration to automatically track and update DNS records when the public IP changes.
For secure remote access to the entire LAN without exposing individual services, see Part 8 for WireGuard VPN deployment.