Post

OpenVPN: Restrict Client Access to Specific IPs Only

How to restrict OpenVPN clients to a limited set of destination IPs, using client-config-dir routes, a client-connect script, and iptables rules.

OpenVPN’s default behavior is full network access: once a client connects, it can reach anything routable through the tunnel. That’s not what you want for a contractor VPN, a support tunnel, or any profile that only needs a handful of internal services. Restricting a client to specific IPs takes three pieces working together: a client-config-dir route, a script that runs on connect, and iptables rules that actually enforce it.

Prerequisites

  • A working OpenVPN server
  • Root or sudo access to the server
  • Basic knowledge of networking and firewall concepts

Server configuration

Edit /etc/openvpn/server.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# DNS setup
push "dhcp-option DNS 10.8.0.1"
push "redirect-gateway def1 bypass-dhcp"

# This helps prevent DNS leaks on Windows
push "block-outside-dns"

# Client configuration directory
client-config-dir /etc/openvpn/ccd
route 10.8.0.0 255.255.255.0

# Logging
log-append /var/log/openvpn.log
status /var/log/openvpn-status.log

# Enable running external scripts
script-security 2
client-connect /etc/openvpn/client-connect.sh

Create the client config directory:

1
2
3
sudo mkdir -p /etc/openvpn/ccd
sudo chown nobody:nogroup /etc/openvpn/ccd
sudo chmod 755 /etc/openvpn/ccd

Push the specific routes clients are allowed to use:

1
2
push "route 192.168.1.0 255.255.255.0"
push "route 10.0.0.5 255.255.255.255"

Enforcing the restriction with a client-connect script

Pushed routes tell the client which networks to send through the tunnel; they don’t stop the server from forwarding traffic anywhere else. The actual enforcement happens in iptables, applied by a script that runs every time a client connects.

List of allowed destination IPs:

1
2
3
echo "192.168.1.100
10.0.0.50
203.0.113.10" > /etc/openvpn/allowed_ips.txt

/etc/openvpn/client-connect.sh:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

# Read the allowed IPs
ALLOWED_IPS=$(cat /etc/openvpn/allowed_ips.txt)

# Create iptables rules for each allowed IP
for IP in $ALLOWED_IPS; do
    iptables -A FORWARD -i tun+ -d $IP -j ACCEPT
done

# Drop all other forward traffic from tun interfaces
iptables -A FORWARD -i tun+ -j DROP
1
chmod +x /etc/openvpn/client-connect.sh

Managing rules directly with iptables

If you’d rather manage rules by hand instead of through the connect script:

1
2
3
4
5
6
7
# Allow all traffic for other VPN clients
sudo iptables -A FORWARD -i tun0 -o eth0 -s 10.8.0.0/24 -j ACCEPT

# Rules for a specific client (10.8.0.5)
sudo iptables -A FORWARD -i tun0 -o eth0 -s 10.8.0.5 -d 93.184.216.34 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -o eth0 -s 10.8.0.5 -p udp --dport 53 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -o eth0 -s 10.8.0.5 -j DROP
1
2
3
4
5
6
# View current rules
sudo iptables -L -v -n

# Delete a specific rule
sudo iptables -L --line-numbers
sudo iptables -D CHAIN_NAME RULE_NUMBER

Back up and restore:

1
2
sudo iptables-save > /tmp/iptables.rules
sudo iptables-restore < /tmp/iptables.rules

A backup script, if you want this on a cron job:

1
2
3
4
5
6
#!/bin/bash
BACKUP_DIR="/path/to/backup/directory"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_FILE="$BACKUP_DIR/iptables_backup_$TIMESTAMP.rules"
iptables-save > "$BACKUP_FILE"
echo "Backup saved to $BACKUP_FILE"

Per-client configuration

To give one client its own IP and routes, create a file in ccd named after the client:

1
sudo nano /etc/openvpn/ccd/client1
1
2
3
4
5
# Assign a specific IP to the client
ifconfig-push 10.8.0.200 255.255.255.255

# Push specific routes to this client
push "route 192.168.1.0 255.255.255.0"
1
2
sudo chown nobody:nogroup /etc/openvpn/ccd/client1
sudo chmod 644 /etc/openvpn/ccd/client1

Verifying it worked

1
2
3
sudo systemctl status openvpn
# or
sudo service openvpn status
1
2
3
4
5
6
7
8
9
10
11
# Check routing table
netstat -r
ip route

# Check tun interface
ifconfig tun0
ip addr show tun0

# Check OpenVPN connections
ss -anp | grep openvpn
netstat -anp | grep openvpn
1
tail -f /var/log/openvpn.log

What this buys you, and what it doesn’t

Pushed routes and iptables rules restrict where traffic can go, but they’re enforced on the server, not the client: a compromised or misconfigured client is still authenticated, just unable to reach much. That’s the right trade-off for a contractor or support tunnel. It isn’t a substitute for per-client certificates and proper key management, which this setup assumes you’ve already solved.

This post is licensed under CC BY 4.0 by the author.