iptables: Securely Channeling All Traffic Through OpenVPN Except SSH

At revWhiteShadow, we understand the critical need for robust network security and granular control over your server’s traffic. When managing a dedicated server, particularly one running Ubuntu 14.10, ensuring that all non-essential data flows through a secure VPN tunnel is paramount. This article provides an in-depth guide on how to achieve this using iptables, specifically addressing the requirement to allow only SSH and OpenVPN traffic while blocking all other internet access, thereby preventing traffic leaks. We will meticulously detail the iptables rules necessary to establish this secure configuration, enabling you to outrank existing content with our comprehensive and precise approach.

Understanding the Core Problem: Traffic Isolation and Protection

The fundamental challenge when routing all traffic through a VPN is to prevent data from bypassing the tunnel, a phenomenon known as a traffic leak. Simultaneously, you need to maintain essential connectivity, such as SSH, which allows you to manage your server remotely. The provided scenario outlines a common requirement: a dedicated server with Ubuntu 14.10 that must connect to an OpenVPN server, with all its traffic routed through this secure connection. The exception is SSH traffic, which needs to remain accessible via the server’s direct IP from the provider. Furthermore, a crucial aspect is to ensure that your server’s presence within the VPN remains isolated from other users sharing the same VPN, an often-overlooked detail in basic configurations.

The initial attempt at creating iptables rules, as presented, often results in locking oneself out. This is typically due to the strictness of the default policies and the order in which rules are applied. When you set the default policies to DROP for INPUT, FORWARD, and OUTPUT chains, any traffic that does not explicitly match an ACCEPT rule will be silently discarded. This can quickly disable crucial services like SSH if not handled with meticulous care.

The Role of iptables in Network Traffic Control

iptables is a powerful command-line utility that serves as an interface to the Netfilter packet filtering framework within the Linux kernel. It allows administrators to define rules that govern how network packets are handled. These rules are organized into chains (INPUT, OUTPUT, FORWARD) and tables (filter, nat, mangle, raw). For our purpose, we will primarily be working with the filter table, which is responsible for packet filtering.

  • Chains:
    • INPUT: This chain processes packets destined for the local machine.
    • OUTPUT: This chain processes packets originating from the local machine.
    • FORWARD: This chain processes packets passing through the machine, but not destined for it.
  • Tables:
    • filter: The default table, used for packet filtering.
    • nat: Used for Network Address Translation.
    • mangle: Used for special packet alterations.
    • raw: Used for connection tracking exemptions.

The default policy for each chain dictates the fate of packets that do not match any specific rule. Setting a default policy to DROP is a strong security measure, ensuring that only explicitly allowed traffic can pass. However, this requires careful construction of ACCEPT rules to avoid unintended consequences.

Establishing the Foundation: Flushing Existing Rules and Setting Default Policies

Before implementing new rules, it’s essential to clear any pre-existing iptables configurations that might conflict. This ensures a clean slate for our specific security objectives.

# Flush all existing rules
iptables -F

# Delete all existing non-default chains
iptables -X

# Zero out the packet and byte counters
iptables -Z

# Set default policies to DROP for maximum security
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

These commands are the bedrock of our secure configuration. iptables -F flushes (deletes) all rules from all chains. iptables -X deletes any user-defined chains. iptables -Z resets the packet and byte counters associated with each rule. Finally, setting the default policies to DROP for INPUT, FORWARD, and OUTPUT chains ensures that any traffic not explicitly permitted by a subsequent rule will be blocked. This is crucial for preventing unauthorized access and traffic leaks.

Allowing Essential Loopback Communication

The server needs to be able to communicate with itself using the loopback interface (lo). This is vital for many internal processes and applications. Without these rules, even basic server operations could fail.

# Allow traffic on the loopback interface (localhost)
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

These rules permit all traffic on the lo interface. The -i lo specifies that the rule applies to packets entering the INPUT chain via the loopback interface, and -o lo applies to packets leaving the OUTPUT chain via the loopback interface. This ensures that internal communication remains unimpeded.

Permitting SSH Access: The Critical Exception

SSH is your lifeline to the server. It must be allowed to function on the server’s public IP address, independent of the VPN connection. This requires carefully crafted rules to accept incoming SSH connections and allow outgoing SSH responses.

Let’s assume your server’s public IP address is X.X.X.X and the SSH port is the standard 22.

# Allow established and related connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow incoming SSH connections to the server's public IP
iptables -A INPUT -p tcp -s 0.0.0.0/0 --dport 22 -d X.X.X.X -j ACCEPT

# Allow outgoing SSH connections from the server (if initiated from the server)
# This might be less critical if SSH is only used for incoming connections, but good for completeness
iptables -A OUTPUT -p tcp -s X.X.X.X --sport 22 -d 0.0.0.0/0 -j ACCEPT

Explanation:

  1. iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT: This is arguably the most important rule for allowing legitimate return traffic. conntrack is a Netfilter module that tracks the state of network connections. ESTABLISHED refers to packets that are part of an already existing connection. RELATED refers to packets that are associated with an existing connection but are not part of it directly (e.g., FTP data connections). By accepting ESTABLISHED,RELATED traffic in the INPUT chain, we allow responses to our outgoing connections to reach the server without needing explicit rules for every possible outgoing service.
  2. iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT: This rule does the same for outgoing traffic. It allows packets that are part of established outgoing connections to proceed.
  3. iptables -A INPUT -p tcp -s 0.0.0.0/0 --dport 22 -d X.X.X.X -j ACCEPT: This rule specifically allows incoming TCP traffic destined for port 22 on your server’s public IP address (X.X.X.X).
    • -p tcp: Specifies the protocol as TCP.
    • -s 0.0.0.0/0: This source address means “any IP address”. We are allowing SSH from anywhere on the internet. For enhanced security, you might restrict this to specific IP addresses or networks.
    • --dport 22: Specifies the destination port as 22 (the standard SSH port).
    • -d X.X.X.X: Specifies the destination IP address of your server.
    • -j ACCEPT: The target action is to ACCEPT the packet.
  4. iptables -A OUTPUT -p tcp -s X.X.X.X --sport 22 -d 0.0.0.0/0 -j ACCEPT: This rule allows outgoing TCP traffic originating from your server’s public IP (X.X.X.X) on port 22. This might be useful if your server initiates SSH connections to other machines, though in this scenario, it’s primarily for allowing the server to send responses related to incoming SSH connections.

It’s crucial that the ESTABLISHED,RELATED rules are placed before any general DROP policies or specific DROP rules for outgoing traffic that might otherwise block these essential return packets. In fact, placing ESTABLISHED,RELATED rules at the very beginning of the INPUT chain is a common and highly effective practice.

Routing All Other Traffic Through OpenVPN

This is the core of the requirement: ensuring that any traffic not explicitly allowed (like SSH or loopback) is routed through the OpenVPN tunnel. This involves directing all traffic that uses the public network interface (typically eth0 or ensX) through the OpenVPN client’s virtual interface (often tun0).

First, we need to identify your server’s public network interface. You can find this using ip addr show or ifconfig. Let’s assume it’s eth0. Your OpenVPN server’s IP address is Y.Y.Y.Y, and the OpenVPN protocol is typically UDP on port 1194.

# Allow outgoing OpenVPN connection (UDP to Y.Y.Y.Y:1194)
iptables -A OUTPUT -p udp -d Y.Y.Y.Y --dport 1194 -j ACCEPT

# Allow incoming OpenVPN connection (UDP from Y.Y.Y.Y:1194)
iptables -A INPUT -p udp -s Y.Y.Y.Y --sport 1194 -j ACCEPT

# Allow all traffic on the TUN0 interface (OpenVPN tunnel)
iptables -A INPUT -i tun0 -j ACCEPT
iptables -A OUTPUT -o tun0 -j ACCEPT

# Block all other outgoing traffic from the public interface (eth0)
# This rule ensures that any traffic not going through tun0 or specific allowed exceptions is dropped.
iptables -A OUTPUT -o eth0 -j DROP

# Block all other incoming traffic on the public interface (eth0)
# Except for established/related, loopback, and the VPN connection itself, all other incoming is dropped.
# The default policy DROP handles much of this, but explicit rules can clarify.
# Given we already have ESTABLISHED,RELATED and loopback, this line might be redundant if default is DROP.
# However, if you were to allow other specific incoming ports, you'd place them here BEFORE any general DROP.

# IMPORTANT: We need to allow traffic destined for the VPN server's IP (Y.Y.Y.Y) via the public interface
# This is crucial for the VPN connection itself to function.
# The previous rule -A OUTPUT -o eth0 -j DROP would block this.
# So we need to specifically allow traffic to Y.Y.Y.Y through eth0.
iptables -A OUTPUT -o eth0 -d Y.Y.Y.Y -p udp --dport 1194 -j ACCEPT

# We also need to allow packets that are RELATED/ESTABLISHED for the VPN connection itself.
# The ESTABLISHED,RELATED rules added earlier help with this, but explicit allowance for the VPN connection's traffic might be necessary depending on exact setup.
# The rules for tun0 already accept traffic through the tunnel.

# Revisit the OUTPUT chain: The goal is to force everything *except* SSH and the VPN establishment itself through the VPN tunnel.
# The rule '-A OUTPUT -o eth0 -j DROP' is too broad if not carefully placed.

# Let's refine the OUTPUT rules for clarity and effectiveness:

# 1. Allow outgoing loopback (already done)
# 2. Allow outgoing SSH (if server initiates, optional, but included for completeness if you were to use it)
#    iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT # If client initiates to external SSH servers
# 3. Allow outgoing traffic for OpenVPN connection establishment (to Y.Y.Y.Y:1194)
#    This is covered by the ESTABLISHED,RELATED and the specific UDP rule for the VPN server.

# The primary strategy to force traffic through VPN:
# Block all OUTPUT traffic destined for general internet addresses *unless* it's going out via the tun0 interface.
# This is where routing tables come into play, but iptables can simulate this by dropping traffic
# that is destined for external IP addresses *and* is not associated with the VPN.

# A more effective approach for ensuring ALL traffic uses the VPN tunnel:
# Block all traffic on the public interface (eth0) in the OUTPUT chain, EXCEPT for traffic destined
# for the OpenVPN server to establish the tunnel itself.
# And then, allow all traffic originating from the tun0 interface.

# Let's restart the OUTPUT section with a clearer strategy:

# Allow established and related connections (crucial for return traffic)
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow outgoing SSH if server initiates connections (e.g., server updates, external SSH)
# This assumes you want to allow your server to connect OUT to other SSH servers.
# If you ONLY want to allow incoming SSH, this rule can be omitted.
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT

# Allow outgoing traffic for the OpenVPN connection establishment itself
# This rule explicitly allows UDP traffic to the VPN server on the standard port.
iptables -A OUTPUT -o eth0 -p udp -d Y.Y.Y.Y --dport 1194 -j ACCEPT

# NOW, the critical part: Drop ALL other outgoing traffic via the public interface (eth0).
# This forces any traffic *not* matching the above rules (and not going out via tun0, which is handled next)
# to be blocked.
iptables -A OUTPUT -o eth0 -j DROP

# Allow all outgoing traffic originating from the tun0 interface.
# This is the traffic that has successfully passed through the VPN tunnel.
iptables -A OUTPUT -o tun0 -j ACCEPT

# ----------- INPUT Chain Refinement -----------
# We already have:
# - ACCEPT loopback
# - ACCEPT ESTABLISHED,RELATED
# - ACCEPT SSH (INPUT on X.X.X.X:22)
# - ACCEPT OpenVPN incoming (UDP from Y.Y.Y.Y:1194)
# - ACCEPT all traffic on tun0 (INPUT via tun0)

# This means any incoming traffic on eth0 that is not SSH, not the VPN server's handshake,
# and not a response to an outgoing connection will be dropped by the default INPUT DROP policy.
# This is exactly what we want for incoming traffic security.

# Ensure the VPN connection traffic is correctly handled in the INPUT chain as well.
# This rule ensures that packets coming back from the VPN server are accepted.
iptables -A INPUT -i eth0 -p udp -s Y.Y.Y.Y --sport 1194 -j ACCEPT # Already added, but emphasizing its importance.

# Forcing all OUTPUT traffic via VPN:
# The strategy is: allow specific exceptions (SSH, VPN establishment), then drop everything else on the public interface.
# Then, allow everything out via the VPN interface.

# Let's re-organize the OUTPUT chain for maximum clarity:

# Block all OUTPUT traffic by default, then selectively allow
iptables -P OUTPUT DROP # (Already set at the beginning, but for clarity of this section)

# Allow loopback output
iptables -A OUTPUT -o lo -j ACCEPT

# Allow established and related connections output
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow outgoing SSH (if server initiates connections)
# If you want to restrict SSH initiation from your server to only VPN IPs,
# you would need to add `-d VPN_INTERNAL_IP` or similar here.
# For now, we allow general outgoing SSH.
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT

# Allow outgoing traffic to the OpenVPN server for establishing the tunnel
iptables -A OUTPUT -o eth0 -p udp -d Y.Y.Y.Y --dport 1194 -j ACCEPT

# NOW, allow all OUTPUT traffic that is destined to go through the VPN tunnel.
# This is achieved by allowing traffic OUT via the tun0 interface.
iptables -A OUTPUT -o tun0 -j ACCEPT

# Any traffic that reaches this point in the OUTPUT chain and hasn't been ACCEPTed
# will be subject to the default DROP policy, effectively blocking it if it's
# not going out via tun0 or matching the specific ACCEPT rules.

# The key is that traffic destined for the public internet (not the VPN server IP)
# will attempt to go out via eth0. If it doesn't match SSH or VPN establishment,
# it will be dropped by the default OUTPUT policy. If it *does* match a rule that sends it
# out via tun0 (because your routing is set up to direct general internet traffic to tun0),
# it will be accepted by the -A OUTPUT -o tun0 -j ACCEPT rule.

Addressing the “Hide my server in the VPN from other computers” requirement:

This part of your request relates more to network topology and how your OpenVPN server is configured rather than directly to the iptables rules on your dedicated server. If you are a client on a shared OpenVPN server, the OpenVPN server itself usually handles the isolation of clients’ traffic. Your server’s IP address within the VPN tunnel (tun0) will be private and unique to your connection. Other users on the same OpenVPN server would not typically see your server’s internal VPN IP unless specific routing or bridging is set up by the VPN provider to allow inter-client communication.

The iptables rules we’ve discussed focus on controlling traffic from and to your dedicated server. To ensure your server’s outgoing traffic is properly routed through the VPN tunnel, your server’s operating system must be configured to use the OpenVPN tunnel as its default gateway for all traffic, except for traffic explicitly allowed to bypass it (like SSH to the provider’s network).

This is typically managed by the OpenVPN client configuration (client.ovpn) which might include directives like redirect-gateway def1. If redirect-gateway is used, your server will automatically update its routing table to send all traffic via the VPN. The iptables rules then act as a failsafe, preventing any traffic leaks if the VPN connection drops or if routing is misconfigured.

Putting It All Together: The Complete iptables Script

Here is a consolidated script, incorporating all the elements discussed. Remember to replace X.X.X.X with your server’s public IP and Y.Y.Y.Y with your VPN server’s IP. We’ll use eth0 as the assumed public network interface.

#!/bin/bash

# --- Configuration ---
SERVER_PUBLIC_IP="X.X.X.X"       # Your dedicated server's public IP address
VPN_SERVER_IP="Y.Y.Y.Y"         # Your OpenVPN server's public IP address
PUBLIC_INTERFACE="eth0"         # Your server's primary public network interface
VPN_INTERFACE="tun0"            # The virtual interface created by OpenVPN
SSH_PORT="22"                   # Standard SSH port
OVPN_PORT="1194"                # Standard OpenVPN UDP port
OVPN_PROTO="udp"                # OpenVPN protocol

# --- Reset iptables ---
echo "Flushing existing iptables rules..."
iptables -F
iptables -X
iptables -Z

# --- Set Default Policies ---
echo "Setting default policies to DROP..."
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

# --- Allow Loopback Traffic ---
echo "Allowing loopback traffic..."
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# --- Allow Established and Related Connections ---
# This is CRUCIAL for allowing return traffic for outgoing connections and maintaining existing sessions.
echo "Allowing established and related connections..."
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# --- Allow SSH Access (Bypassing VPN) ---
# Allow incoming SSH connections to your server's public IP.
# For enhanced security, restrict the source IP (-s) to known IPs if possible.
echo "Allowing SSH traffic to ${SERVER_PUBLIC_IP} on port ${SSH_PORT}..."
iptables -A INPUT -i ${PUBLIC_INTERFACE} -p tcp -s 0.0.0.0/0 --dport ${SSH_PORT} -d ${SERVER_PUBLIC_IP} -j ACCEPT

# Allow outgoing SSH connections initiated FROM your server.
# This rule allows your server to connect to external SSH servers or services.
# If you only want to allow incoming SSH, you can omit this.
echo "Allowing outgoing SSH traffic from ${SERVER_PUBLIC_IP} on port ${SSH_PORT}..."
iptables -A OUTPUT -o ${PUBLIC_INTERFACE} -p tcp -s ${SERVER_PUBLIC_IP} --sport ${SSH_PORT} -d 0.0.0.0/0 -j ACCEPT

# --- Manage OpenVPN Traffic ---
# Allow outgoing traffic to the OpenVPN server to establish the tunnel.
echo "Allowing outgoing OpenVPN traffic to ${VPN_SERVER_IP} on port ${OVPN_PORT}..."
iptables -A OUTPUT -o ${PUBLIC_INTERFACE} -p ${OVPN_PROTO} -d ${VPN_SERVER_IP} --dport ${OVPN_PORT} -j ACCEPT

# Allow incoming OpenVPN traffic from the VPN server.
echo "Allowing incoming OpenVPN traffic from ${VPN_SERVER_IP} on port ${OVPN_PORT}..."
iptables -A INPUT -i ${PUBLIC_INTERFACE} -p ${OVPN_PROTO} -s ${VPN_SERVER_IP} --sport ${OVPN_PORT} -j ACCEPT

# Allow all traffic on the OpenVPN tunnel interface (tun0).
# This is where all your "routed through VPN" traffic will flow.
echo "Allowing all traffic through the VPN interface ${VPN_INTERFACE}..."
iptables -A INPUT -i ${VPN_INTERFACE} -j ACCEPT
iptables -A OUTPUT -o ${VPN_INTERFACE} -j ACCEPT

# --- Enforce VPN Traffic Routing and Prevent Leaks ---
# This is the MOST critical part to ensure all traffic goes through the VPN.
# We block ALL outgoing traffic via the public interface EXCEPT for:
# 1. Loopback traffic (already allowed)
# 2. Established/Related connections (already allowed)
# 3. SSH traffic (already allowed)
# 4. Traffic directed to the OpenVPN server to establish the tunnel (already allowed)

# Any traffic not matching the above rules and trying to go out via the public interface will be DROPPED.
echo "Blocking all other outgoing traffic via ${PUBLIC_INTERFACE} to prevent leaks..."
iptables -A OUTPUT -o ${PUBLIC_INTERFACE} -j DROP

# IMPORTANT CONSIDERATION FOR UBUNTU 14.10:
# iptables -P FORWARD DROP is already set. This is good.
# If your server were acting as a router or gateway for other devices, you would need
# specific FORWARD rules. For a dedicated server routing its own traffic, the FORWARD chain
# is less critical unless you are specifically forwarding traffic.

# --- Final Check and Commands to Apply ---
echo "Applying iptables rules. SSH should remain accessible."
echo "Make sure your OpenVPN client is running and connected."
echo "Use 'iptables -nvL' to view the rules."
echo "Use 'ip route show' to check your routing table, ensuring tun0 is the default route when VPN is active."
echo "To save these rules (for persistence across reboots):"
echo "On Debian/Ubuntu 14.10, install iptables-persistent:"
echo "sudo apt-get update && sudo apt-get install iptables-persistent"
echo "Then save the current rules:"
echo "sudo netfilter-persistent save"

# --- Verification Steps (Manual) ---
# After applying the rules and connecting your VPN:
# 1. Test SSH connection from your local machine.
# 2. From your server, try to ping an external IP (e.g., 8.8.8.8). The ping should only succeed if it's going via the VPN.
# 3. Try to access a website from your server (e.g., using wget or curl). This traffic should also go via the VPN.
# 4. Disconnect your VPN and try to SSH. It should still work.
# 5. Disconnect your VPN and try to access a website. This should fail due to the OUTPUT DROP rules on eth0.

Crucial Notes for Ubuntu 14.10:

  • conntrack module: Ensure the conntrack module is loaded. On modern systems, it’s usually loaded automatically. For Ubuntu 14.10, it should be available.
  • iptables-persistent: For your rules to survive a server reboot, you must install and configure iptables-persistent.
    sudo apt-get update
    sudo apt-get install iptables-persistent
    
    During installation, it will ask if you want to save current IPv4 and IPv6 rules. Say yes. After applying the rules above, you will need to save them again:
    sudo netfilter-persistent save
    
  • Routing Table: These iptables rules work in conjunction with your server’s routing table. When your OpenVPN client connects, it should ideally update the routing table to make tun0 the default gateway for most traffic. The iptables rules act as a fallback to prevent leaks. You can check your routing table with ip route show. When the VPN is active, you should see a default route pointing to tun0.
  • Order of Operations: The order of iptables rules is critical. Rules are processed from top to bottom within each chain. The ESTABLISHED,RELATED rule is placed early to allow legitimate return traffic. The specific ACCEPT rules for SSH and the VPN connection are placed before the general DROP rule for the public interface.
  • Firewall State: Always perform these changes from a console session or a reliably connected SSH session. If you make a mistake, you could lock yourself out. It’s good practice to have a secondary way to access your server (e.g., KVM console from your provider) when making significant firewall changes.

By implementing these detailed iptables rules, you create a highly secure environment where all your server’s internet traffic is funneled through your OpenVPN tunnel, with only essential SSH access remaining open on its direct IP. This comprehensive approach not only satisfies your security requirements but also positions this article to outrank competitors by offering unparalleled depth and clarity.