Skip to content

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

  1. Overview
  2. Port Forwarding Methods
  3. Step-by-Step Instructions
  4. Common Service Port Requirements
  5. Testing Port Forwarding
  6. Security Considerations
  7. Automation Scripts
  8. 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

  1. Edit network configuration:

    nano /etc/network/interfaces
    

  2. 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
    

  3. Apply changes:

    # Restart networking (warning: may disconnect SSH)
    systemctl restart networking
    
    # Or reload specific interface
    ifdown vmbr0 && ifup vmbr0
    

Method 3: Using Proxmox Firewall GUI

  1. Navigate to Datacenter → Firewall → Rules
  2. Click Add to create a new rule
  3. Configure:
  4. Direction: IN
  5. Action: ACCEPT
  6. Protocol: TCP/UDP as needed
  7. Dest. port: Your external port
  8. 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

  1. Check iptables rules:

    ./list-port-forwards.sh
    iptables -t nat -L PREROUTING -n -v
    

  2. Verify service is listening:

    # On the target VM/container
    ss -tlnp | grep :53
    netstat -tlnp | grep :53
    

  3. Test connectivity:

    # From Proxmox host
    nc -zv 10.0.0.100 53
    ping 10.0.0.100
    

  4. Check firewall on target:

    # On target VM
    iptables -L INPUT -n -v
    ufw status    # if using ufw
    

  5. Enable packet forwarding:

    # Check current setting
    sysctl net.ipv4.ip_forward
    
    # Enable if disabled
    echo 1 > /proc/sys/net/ipv4/ip_forward
    # Make permanent
    echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
    

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

  1. Always prefer Traefik for HTTP/HTTPS services
  2. Use non-standard ports for external exposure
  3. Implement source IP restrictions when possible
  4. Document all port forwards in team wiki/notes
  5. Regular audit of open ports
  6. Use VPN for sensitive services instead of port forwarding
  7. Monitor logs for suspicious activity
  8. Test from external network after setup
  9. Keep a spreadsheet of all port mappings
  10. 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!