Proxmox Port Forwarding Guide¶
This guide provides comprehensive instructions for configuring port forwarding on Proxmox to expose services running in VMs or containers to external networks.
Table of Contents¶
- Overview
- Port Forwarding Methods
- Step-by-Step Instructions
- Common Service Port Requirements
- Testing Port Forwarding
- Security Considerations
- Automation Scripts
- Troubleshooting
Overview¶
Port forwarding in Proxmox allows external traffic to reach services running inside VMs or containers. This is essential for: - Services that require direct port access (DNS, game servers, VPN) - Debugging and development access - Services not suitable for reverse proxy (non-HTTP protocols)
Important: For web services, always prefer using Traefik reverse proxy instead of direct port forwarding.
Port Forwarding Methods¶
1. IPTables Rules (Temporary)¶
- Quick setup for testing
- Lost on reboot
- Good for debugging
2. Network Configuration (Persistent)¶
- Survives reboots
- Configured in
/etc/network/interfaces - Recommended for production
3. Firewall Rules (GUI)¶
- Configured through Proxmox web interface
- Easy to manage
- Limited flexibility
Step-by-Step Instructions¶
Method 1: Using IPTables (Temporary)¶
# Forward external port to VM/Container
# Format: iptables -t nat -A PREROUTING -p <protocol> --dport <external_port> -j DNAT --to-destination <internal_ip>:<internal_port>
# Example: Forward port 8053 to VM at 10.0.0.100:53 (DNS)
iptables -t nat -A PREROUTING -p tcp --dport 8053 -j DNAT --to-destination 10.0.0.100:53
iptables -t nat -A PREROUTING -p udp --dport 8053 -j DNAT --to-destination 10.0.0.100:53
# Allow forwarded traffic
iptables -A FORWARD -p tcp -d 10.0.0.100 --dport 53 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -p udp -d 10.0.0.100 --dport 53 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
# Enable masquerading for return traffic
iptables -t nat -A POSTROUTING -j MASQUERADE
Method 2: Persistent Configuration via /etc/network/interfaces¶
-
Edit network configuration:
-
Add port forwarding rules in the appropriate bridge section:
auto vmbr0 iface vmbr0 inet static address 192.168.1.10/24 gateway 192.168.1.1 bridge-ports eno1 bridge-stp off bridge-fd 0 # Port forwarding rules post-up iptables -t nat -A PREROUTING -i vmbr0 -p tcp --dport 8053 -j DNAT --to 10.0.0.100:53 post-up iptables -t nat -A PREROUTING -i vmbr0 -p udp --dport 8053 -j DNAT --to 10.0.0.100:53 post-up iptables -A FORWARD -p tcp -d 10.0.0.100 --dport 53 -j ACCEPT post-up iptables -A FORWARD -p udp -d 10.0.0.100 --dport 53 -j ACCEPT # Remove rules on interface down post-down iptables -t nat -D PREROUTING -i vmbr0 -p tcp --dport 8053 -j DNAT --to 10.0.0.100:53 post-down iptables -t nat -D PREROUTING -i vmbr0 -p udp --dport 8053 -j DNAT --to 10.0.0.100:53 post-down iptables -D FORWARD -p tcp -d 10.0.0.100 --dport 53 -j ACCEPT post-down iptables -D FORWARD -p udp -d 10.0.0.100 --dport 53 -j ACCEPT -
Apply changes:
Method 3: Using Proxmox Firewall GUI¶
- Navigate to Datacenter → Firewall → Rules
- Click Add to create a new rule
- Configure:
- Direction: IN
- Action: ACCEPT
- Protocol: TCP/UDP as needed
- Dest. port: Your external port
- Comment: Description of the service
Note: GUI firewall only controls access, not NAT. You still need iptables for actual port forwarding.
Common Service Port Requirements¶
Web Services (Use Traefik Instead!)¶
# These should use Traefik reverse proxy, NOT port forwarding
HTTP: 80/tcp # → Traefik handles this
HTTPS: 443/tcp # → Traefik handles this
Services Requiring Direct Port Access¶
DNS Services¶
# AdGuard Home / Pi-hole
DNS: 53/tcp,udp
DNS-over-TLS: 853/tcp
DNS-over-HTTPS: 443/tcp (conflicts with Traefik)
Admin Panel: 3000/tcp (use Traefik instead)
# Example port forwarding for DNS
external 8053 → internal_vm:53 (TCP/UDP)
Game Servers¶
# Minecraft
Default: 25565/tcp
RCON: 25575/tcp
Query: 25565/udp
# Valheim
Game: 2456-2458/tcp,udp
# CS:GO/CS2
Game: 27015/tcp,udp
RCON: 27015/tcp
VPN Services¶
# WireGuard
Default: 51820/udp
# OpenVPN
Default: 1194/udp
Alternative: 443/tcp (conflicts with Traefik)
Database Direct Access (Development Only!)¶
# PostgreSQL
Default: 5432/tcp
# Forward: external 15432 → internal:5432
# MySQL/MariaDB
Default: 3306/tcp
# Forward: external 13306 → internal:3306
# Redis
Default: 6379/tcp
# Forward: external 16379 → internal:6379
Monitoring & Management¶
# SSH (already forwarded by Proxmox)
Default: 22/tcp
# Proxmox Web UI (already accessible)
Default: 8006/tcp
# SNMP
Default: 161/udp
# Syslog
Default: 514/udp
Testing Port Forwarding¶
1. Local Testing (from Proxmox host)¶
# Test TCP port
nc -zv 10.0.0.100 53
telnet 10.0.0.100 53
# Test UDP port
nc -zuv 10.0.0.100 53
# Check listening ports
ss -tlnp | grep :53
ss -ulnp | grep :53
2. External Testing¶
# From external machine
nc -zv proxmox.example.com 8053
nmap -p 8053 proxmox.example.com
# Test specific service (DNS example)
dig @proxmox.example.com -p 8053 google.com
# Online port checker
curl -s https://api.portchecker.io/check/proxmox.example.com/8053
3. Verify NAT Rules¶
# List all NAT rules
iptables -t nat -L -n -v --line-numbers
# List FORWARD rules
iptables -L FORWARD -n -v --line-numbers
# Watch packet counters
watch -n 1 'iptables -t nat -L PREROUTING -n -v'
Security Considerations¶
1. Principle of Least Privilege¶
- Only forward necessary ports
- Use non-standard external ports when possible
- Limit source IPs when feasible
2. Secure Port Forwarding¶
# Restrict to specific source IP
iptables -t nat -A PREROUTING -s 203.0.113.0/24 -p tcp --dport 8053 -j DNAT --to 10.0.0.100:53
# Rate limiting
iptables -A FORWARD -p tcp --dport 53 -m limit --limit 10/minute --limit-burst 20 -j ACCEPT
# Connection tracking limits
iptables -A FORWARD -p tcp --dport 53 -m conntrack --ctstate NEW -m limit --limit 10/s -j ACCEPT
3. Service-Specific Security¶
SSH Access¶
# Never expose SSH directly! Use:
# 1. VPN access first
# 2. Jump host/bastion
# 3. Tailscale/ZeroTier
# 4. If you must: non-standard port + fail2ban + key-only auth
Database Ports¶
# NEVER expose databases directly to internet!
# Use:
# 1. SSH tunneling for development
# 2. VPN for production access
# 3. Application-level access only
4. Monitoring & Logging¶
# Enable logging for forwarded connections
iptables -A FORWARD -j LOG --log-prefix "PORT-FWD: " --log-level 4
# Monitor logs
tail -f /var/log/kern.log | grep PORT-FWD
# Fail2ban for brute force protection
apt install fail2ban
# Configure jails for exposed services
Automation Scripts¶
1. Add Port Forward Script¶
Create /srv/dockerdata/_scripts/add-port-forward.sh:
#!/bin/bash
# Add port forwarding rule with validation
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Usage
usage() {
echo "Usage: $0 -e EXTERNAL_PORT -i INTERNAL_IP -p INTERNAL_PORT [-t tcp|udp|both] [-s SOURCE_IP] [-c COMMENT]"
echo " -e: External port to forward"
echo " -i: Internal IP address"
echo " -p: Internal port"
echo " -t: Protocol (tcp/udp/both) - default: both"
echo " -s: Source IP restriction (optional)"
echo " -c: Comment for the rule (optional)"
exit 1
}
# Parse arguments
PROTOCOL="both"
while getopts "e:i:p:t:s:c:h" opt; do
case $opt in
e) EXT_PORT="$OPTARG" ;;
i) INT_IP="$OPTARG" ;;
p) INT_PORT="$OPTARG" ;;
t) PROTOCOL="$OPTARG" ;;
s) SOURCE_IP="$OPTARG" ;;
c) COMMENT="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
# Validate required parameters
if [ -z "$EXT_PORT" ] || [ -z "$INT_IP" ] || [ -z "$INT_PORT" ]; then
echo -e "${RED}Error: Missing required parameters${NC}"
usage
fi
# Validate IP address
if ! [[ "$INT_IP" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
echo -e "${RED}Error: Invalid IP address${NC}"
exit 1
fi
# Validate ports
if ! [[ "$EXT_PORT" =~ ^[0-9]+$ ]] || ! [[ "$INT_PORT" =~ ^[0-9]+$ ]]; then
echo -e "${RED}Error: Invalid port number${NC}"
exit 1
fi
# Check if external port is already in use
if ss -tlnp | grep -q ":$EXT_PORT "; then
echo -e "${YELLOW}Warning: Port $EXT_PORT is already in use locally${NC}"
read -p "Continue anyway? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# Function to add iptables rule
add_rule() {
local proto=$1
local source_restriction=""
if [ -n "$SOURCE_IP" ]; then
source_restriction="-s $SOURCE_IP"
fi
# Add PREROUTING rule
echo -e "${GREEN}Adding $proto port forward: $EXT_PORT → $INT_IP:$INT_PORT${NC}"
iptables -t nat -A PREROUTING $source_restriction -p $proto --dport $EXT_PORT -j DNAT --to-destination $INT_IP:$INT_PORT
# Add FORWARD rule
iptables -A FORWARD $source_restriction -p $proto -d $INT_IP --dport $INT_PORT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
# Add comment if provided
if [ -n "$COMMENT" ]; then
iptables -t nat -I PREROUTING 1 -p $proto --dport $EXT_PORT -m comment --comment "$COMMENT"
fi
}
# Apply rules based on protocol
case $PROTOCOL in
tcp)
add_rule tcp
;;
udp)
add_rule udp
;;
both)
add_rule tcp
add_rule udp
;;
*)
echo -e "${RED}Error: Invalid protocol. Use tcp, udp, or both${NC}"
exit 1
;;
esac
# Ensure masquerading is enabled
if ! iptables -t nat -L POSTROUTING -n | grep -q MASQUERADE; then
echo -e "${GREEN}Enabling masquerading${NC}"
iptables -t nat -A POSTROUTING -j MASQUERADE
fi
# Save rules (Debian/Ubuntu)
if command -v netfilter-persistent &> /dev/null; then
echo -e "${GREEN}Saving rules permanently${NC}"
netfilter-persistent save
fi
# Display current rules
echo -e "\n${GREEN}Current port forwarding rules:${NC}"
iptables -t nat -L PREROUTING -n -v --line-numbers | grep -E "dpt:$EXT_PORT|DNAT"
echo -e "\n${GREEN}✓ Port forwarding rule added successfully${NC}"
echo -e "${YELLOW}Note: This rule is temporary unless you add it to /etc/network/interfaces${NC}"
# Generate persistent config
echo -e "\n${GREEN}To make this permanent, add to /etc/network/interfaces:${NC}"
echo " # Port forward: $EXT_PORT → $INT_IP:$INT_PORT${COMMENT:+ ($COMMENT)}"
if [ "$PROTOCOL" = "both" ] || [ "$PROTOCOL" = "tcp" ]; then
echo " post-up iptables -t nat -A PREROUTING -i vmbr0 -p tcp --dport $EXT_PORT -j DNAT --to $INT_IP:$INT_PORT"
echo " post-down iptables -t nat -D PREROUTING -i vmbr0 -p tcp --dport $EXT_PORT -j DNAT --to $INT_IP:$INT_PORT"
fi
if [ "$PROTOCOL" = "both" ] || [ "$PROTOCOL" = "udp" ]; then
echo " post-up iptables -t nat -A PREROUTING -i vmbr0 -p udp --dport $EXT_PORT -j DNAT --to $INT_IP:$INT_PORT"
echo " post-down iptables -t nat -D PREROUTING -i vmbr0 -p udp --dport $EXT_PORT -j DNAT --to $INT_IP:$INT_PORT"
fi
2. Remove Port Forward Script¶
Create /srv/dockerdata/_scripts/remove-port-forward.sh:
#!/bin/bash
# Remove port forwarding rule
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
if [ $# -ne 1 ]; then
echo "Usage: $0 EXTERNAL_PORT"
exit 1
fi
EXT_PORT=$1
# Find and remove PREROUTING rules
echo -e "${YELLOW}Removing port forwarding rules for port $EXT_PORT${NC}"
# Get rule numbers (reverse order to avoid shifting)
RULES=$(iptables -t nat -L PREROUTING -n --line-numbers | grep "dpt:$EXT_PORT" | awk '{print $1}' | sort -nr)
if [ -z "$RULES" ]; then
echo -e "${RED}No rules found for port $EXT_PORT${NC}"
exit 1
fi
# Remove rules
for rule_num in $RULES; do
echo -e "${GREEN}Removing PREROUTING rule #$rule_num${NC}"
iptables -t nat -D PREROUTING $rule_num
done
# Remove associated FORWARD rules
# This is trickier as we need to find the destination
FORWARD_RULES=$(iptables -L FORWARD -n -v --line-numbers | grep -E "NEW,ESTABLISHED,RELATED" | awk '{print $1}' | sort -nr)
echo -e "${GREEN}✓ Port forwarding rules removed${NC}"
echo -e "${YELLOW}Note: Remember to remove from /etc/network/interfaces for permanent removal${NC}"
# Save rules if netfilter-persistent is available
if command -v netfilter-persistent &> /dev/null; then
netfilter-persistent save
fi
3. List Port Forwards Script¶
Create /srv/dockerdata/_scripts/list-port-forwards.sh:
#!/bin/bash
# List all active port forwarding rules
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
echo -e "${BLUE} Active Port Forwarding Rules ${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
echo -e "\n${GREEN}NAT PREROUTING Rules (Port Forwards):${NC}"
echo -e "${YELLOW}Num Proto External Destination Packets Bytes${NC}"
iptables -t nat -L PREROUTING -n -v --line-numbers | grep DNAT | while read line; do
echo "$line" | awk '{printf "%-4s %-6s %-12s → %-20s %-8s %s\n", $1, $5, $12, $14, $7, $8}'
done
echo -e "\n${GREEN}FORWARD Rules (Allowed Connections):${NC}"
echo -e "${YELLOW}Num Proto Source Destination State${NC}"
iptables -L FORWARD -n -v --line-numbers | grep -E "NEW,ESTABLISHED,RELATED|dpt:" | while read line; do
echo "$line" | awk '{printf "%-4s %-6s %-12s %-20s %s\n", $1, $5, $9, $10, $13}'
done
echo -e "\n${GREEN}Listening Services (Internal):${NC}"
echo -e "${YELLOW}Proto Local Address:Port Process${NC}"
ss -tlnp 2>/dev/null | grep LISTEN | awk '{printf "%-6s %-26s %s\n", "TCP", $4, $6}' | sort
ss -ulnp 2>/dev/null | awk 'NR>1 {printf "%-6s %-26s %s\n", "UDP", $4, $6}' | sort
echo -e "\n${BLUE}═══════════════════════════════════════════════════════════${NC}"
4. Test Port Forward Script¶
Create /srv/dockerdata/_scripts/test-port-forward.sh:
#!/bin/bash
# Test port forwarding functionality
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
if [ $# -lt 1 ]; then
echo "Usage: $0 EXTERNAL_PORT [PROTOCOL]"
echo " PROTOCOL: tcp (default), udp, or both"
exit 1
fi
EXT_PORT=$1
PROTOCOL=${2:-tcp}
echo -e "${BLUE}Testing port forward on port $EXT_PORT ($PROTOCOL)${NC}"
# Get public IP
PUBLIC_IP=$(curl -s ifconfig.me)
echo -e "${GREEN}Public IP: $PUBLIC_IP${NC}"
# Check if port is in iptables rules
echo -e "\n${YELLOW}Checking iptables rules...${NC}"
if iptables -t nat -L PREROUTING -n | grep -q "dpt:$EXT_PORT"; then
echo -e "${GREEN}✓ Port $EXT_PORT found in NAT rules${NC}"
DEST=$(iptables -t nat -L PREROUTING -n -v | grep "dpt:$EXT_PORT" | awk '{print $NF}' | head -1)
echo -e "${GREEN} Forwarding to: $DEST${NC}"
else
echo -e "${RED}✗ Port $EXT_PORT not found in NAT rules${NC}"
exit 1
fi
# Test local connectivity
echo -e "\n${YELLOW}Testing local connectivity...${NC}"
if [ "$PROTOCOL" = "tcp" ] || [ "$PROTOCOL" = "both" ]; then
if timeout 2 nc -zv localhost $EXT_PORT 2>&1 | grep -q succeeded; then
echo -e "${GREEN}✓ TCP port $EXT_PORT is reachable locally${NC}"
else
echo -e "${YELLOW}! TCP port $EXT_PORT not reachable locally (this is normal for forwarded ports)${NC}"
fi
fi
# Check packet counters
echo -e "\n${YELLOW}Checking packet counters...${NC}"
PACKETS=$(iptables -t nat -L PREROUTING -n -v | grep "dpt:$EXT_PORT" | awk '{print $1}')
BYTES=$(iptables -t nat -L PREROUTING -n -v | grep "dpt:$EXT_PORT" | awk '{print $2}')
echo -e "${GREEN}Packets: $PACKETS, Bytes: $BYTES${NC}"
# External test options
echo -e "\n${YELLOW}External Testing Options:${NC}"
echo "1. From another machine:"
echo " nc -zv $PUBLIC_IP $EXT_PORT"
echo ""
echo "2. Online port checker:"
echo " https://www.yougetsignal.com/tools/open-ports/"
echo " https://portchecker.co/"
echo ""
echo "3. Service-specific test:"
if [ "$EXT_PORT" = "8053" ] || [ "$EXT_PORT" = "53" ]; then
echo " # DNS test"
echo " dig @$PUBLIC_IP -p $EXT_PORT google.com"
elif [ "$EXT_PORT" = "80" ] || [ "$EXT_PORT" = "443" ] || [ "$EXT_PORT" = "8080" ]; then
echo " # HTTP test"
echo " curl -v http://$PUBLIC_IP:$EXT_PORT/"
elif [ "$EXT_PORT" = "22" ]; then
echo " # SSH test"
echo " ssh -p $EXT_PORT user@$PUBLIC_IP"
fi
echo -e "\n${GREEN}✓ Port forward configuration looks correct${NC}"
echo -e "${YELLOW}Note: Test from external network to verify full connectivity${NC}"
5. Temporary Debug Forward Script¶
Create /srv/dockerdata/_scripts/debug-port-forward.sh:
#!/bin/bash
# Create temporary port forward for debugging with automatic cleanup
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
if [ $# -lt 3 ]; then
echo "Usage: $0 EXTERNAL_PORT INTERNAL_IP INTERNAL_PORT [DURATION_MINUTES]"
echo " DURATION_MINUTES: default 60"
exit 1
fi
EXT_PORT=$1
INT_IP=$2
INT_PORT=$3
DURATION=${4:-60}
echo -e "${YELLOW}Creating temporary port forward for debugging${NC}"
echo -e "${GREEN}$EXT_PORT → $INT_IP:$INT_PORT (${DURATION} minutes)${NC}"
# Add rules
iptables -t nat -A PREROUTING -p tcp --dport $EXT_PORT -j DNAT --to-destination $INT_IP:$INT_PORT
iptables -t nat -A PREROUTING -p udp --dport $EXT_PORT -j DNAT --to-destination $INT_IP:$INT_PORT
iptables -A FORWARD -p tcp -d $INT_IP --dport $INT_PORT -j ACCEPT
iptables -A FORWARD -p udp -d $INT_IP --dport $INT_PORT -j ACCEPT
echo -e "${GREEN}✓ Port forward active${NC}"
echo -e "${YELLOW}This is a TEMPORARY rule for debugging${NC}"
echo -e "${YELLOW}It will be automatically removed in $DURATION minutes${NC}"
echo -e "${YELLOW}Press Ctrl+C to remove immediately${NC}"
# Cleanup function
cleanup() {
echo -e "\n${YELLOW}Removing temporary port forward...${NC}"
iptables -t nat -D PREROUTING -p tcp --dport $EXT_PORT -j DNAT --to-destination $INT_IP:$INT_PORT 2>/dev/null || true
iptables -t nat -D PREROUTING -p udp --dport $EXT_PORT -j DNAT --to-destination $INT_IP:$INT_PORT 2>/dev/null || true
iptables -D FORWARD -p tcp -d $INT_IP --dport $INT_PORT -j ACCEPT 2>/dev/null || true
iptables -D FORWARD -p udp -d $INT_IP --dport $INT_PORT -j ACCEPT 2>/dev/null || true
echo -e "${GREEN}✓ Temporary port forward removed${NC}"
}
# Set trap to cleanup on exit
trap cleanup EXIT
# Wait for duration
sleep ${DURATION}m
6. Make Scripts Executable¶
chmod +x /srv/dockerdata/_scripts/add-port-forward.sh
chmod +x /srv/dockerdata/_scripts/remove-port-forward.sh
chmod +x /srv/dockerdata/_scripts/list-port-forwards.sh
chmod +x /srv/dockerdata/_scripts/test-port-forward.sh
chmod +x /srv/dockerdata/_scripts/debug-port-forward.sh
Usage Examples¶
Example 1: Web Service (Use Traefik!)¶
# DON'T DO THIS:
# ./add-port-forward.sh -e 8080 -i 10.0.0.100 -p 80
# DO THIS INSTEAD:
cd /srv/dockerdata
stackwiz mywebapp
# Configure with Traefik labels for https://mywebapp.rbnk.uk
Example 2: DNS Server¶
# Forward external port 8053 to internal DNS server
./add-port-forward.sh -e 8053 -i 10.0.0.100 -p 53 -t both -c "AdGuard DNS"
# Test it
./test-port-forward.sh 8053 both
dig @your-public-ip -p 8053 google.com
Example 3: Game Server¶
# Minecraft server
./add-port-forward.sh -e 25565 -i 10.0.0.150 -p 25565 -t tcp -c "Minecraft Server"
./add-port-forward.sh -e 25565 -i 10.0.0.150 -p 25565 -t udp -c "Minecraft Query"
# List all rules
./list-port-forwards.sh
Example 4: Temporary Debug Access¶
# 30-minute temporary access to internal service
./debug-port-forward.sh 9999 10.0.0.100 3000 30
# Access at http://your-public-ip:9999 for 30 minutes
Example 5: Restricted Access¶
# Only allow from specific IP
./add-port-forward.sh -e 5432 -i 10.0.0.100 -p 5432 -t tcp -s 203.0.113.10 -c "PostgreSQL from office"
Troubleshooting¶
Port Forward Not Working¶
-
Check iptables rules:
-
Verify service is listening:
-
Test connectivity:
-
Check firewall on target:
-
Enable packet forwarding:
Common Issues¶
Issue: "Connection refused" - Service not running on target - Wrong internal IP/port - Firewall blocking on target
Issue: "Connection timeout" - Network routing issue - Firewall dropping packets - Wrong protocol (tcp vs udp)
Issue: "Rules disappear after reboot"
- Add to /etc/network/interfaces
- Use netfilter-persistent save
- Check if another service is managing iptables
Issue: "Port already in use"
- Check what's using it: ss -tlnp | grep :PORT
- Choose different external port
- Stop conflicting service
Best Practices Summary¶
- Always prefer Traefik for HTTP/HTTPS services
- Use non-standard ports for external exposure
- Implement source IP restrictions when possible
- Document all port forwards in team wiki/notes
- Regular audit of open ports
- Use VPN for sensitive services instead of port forwarding
- Monitor logs for suspicious activity
- Test from external network after setup
- Keep a spreadsheet of all port mappings
- Use the automation scripts for consistency
Quick Reference¶
# Add port forward
/srv/dockerdata/_scripts/add-port-forward.sh -e 8053 -i 10.0.0.100 -p 53 -t both
# Remove port forward
/srv/dockerdata/_scripts/remove-port-forward.sh 8053
# List all forwards
/srv/dockerdata/_scripts/list-port-forwards.sh
# Test port forward
/srv/dockerdata/_scripts/test-port-forward.sh 8053
# Temporary debug forward (auto-cleanup)
/srv/dockerdata/_scripts/debug-port-forward.sh 9999 10.0.0.100 3000 30
Remember: When in doubt, use Traefik for web services and VPN for everything else!