Top Tags

Local VM Ubuntu with Proton VPN gateway

Local VM Ubuntu with Proton VPN gateway

Overview

This guide demonstrates how to configure an Ubuntu VM as a VPN gateway using ProtonVPN. This setup allows you to route traffic from other devices through the VPN connection, effectively creating a network-wide VPN gateway. This is particularly useful for:

  • Protecting multiple devices behind a single VPN connection
  • Creating isolated networks for testing or security purposes
  • Routing specific applications or containers through VPN without system-wide VPN configuration
  • Implementing split-tunnel architectures in virtualized environments

Architecture

The setup creates a NAT (Network Address Translation) gateway where:

  1. The Ubuntu VM establishes a VPN tunnel (tun0 interface)
  2. IP forwarding routes packets between your local network and the VPN tunnel
  3. iptables performs NAT masquerading to translate source addresses
  4. Other devices use the VM as their gateway to route traffic through the VPN
[Local Devices] → [Ubuntu VM Gateway] → [VPN Tunnel (tun0)] → [Internet via VPN] ↓ NAT/MASQUERADE ↓ IP Forwarding

IP forward

IP forwarding enables the Linux kernel to route packets between network interfaces. This is essential for the VM to act as a gateway.

bash
1sudo sysctl -w net.ipv4.ip_forward=1
2sudo nano /etc/sysctl.conf
3net.ipv4.ip_forward=1

The first command enables IP forwarding immediately (runtime change). The second command makes it persistent across reboots by adding it to /etc/sysctl.conf.

Verify IP forwarding status:

bash
1# Check current value (1 = enabled, 0 = disabled)
2cat /proc/sys/net/ipv4/ip_forward
3
4# Or using sysctl
5sysctl net.ipv4.ip_forward

Understanding the parameter:

  • net.ipv4.ip_forward=1 - Enables IPv4 packet forwarding
  • For IPv6 support, also enable: net.ipv6.conf.all.forwarding=1

Iptables

iptables is a userspace utility for configuring Linux kernel firewall rules. Here we configure NAT (Network Address Translation) to masquerade outgoing traffic.

bash
1sudo iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE

Breaking down this command:

  • -t nat - Specifies the NAT table
  • -A POSTROUTING - Appends rule to POSTROUTING chain (after routing decision)
  • -o tun0 - Matches packets going out through tun0 interface (VPN tunnel)
  • -j MASQUERADE - Replaces source IP with the interface IP (dynamic SNAT)

Additional iptables rules for comprehensive setup:

bash
1# Allow forwarding from local network (e.g., 192.168.1.0/24) to VPN
2sudo iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT
3
4# Allow established and related connections back
5sudo iptables -A FORWARD -i tun0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
6
7# View current NAT rules
8sudo iptables -t nat -L -n -v
bash
1sudo apt install iptables-persistent
2sudo service netfilter-persistent save
3sudo ufw allow in on tun0 from any to any
4sudo ufw allow out on tun0 from any to any

What these commands do:

  • iptables-persistent - Package that saves and restores iptables rules on boot
  • netfilter-persistent save - Saves current iptables rules to /etc/iptables/rules.v4 and rules.v6
  • UFW (Uncomplicated Firewall) rules allow traffic through the VPN tunnel interface

Verify saved rules:

bash
1# View IPv4 rules
2sudo cat /etc/iptables/rules.v4
3
4# View IPv6 rules
5sudo cat /etc/iptables/rules.v6
6
7# Check UFW status
8sudo ufw status verbose

Ubuntu server

By default all ports are closed. Open it

bash
1sudo iptables -P INPUT ACCEPT
2sudo iptables -P OUTPUT ACCEPT
3sudo iptables -P FORWARD ACCEPT
4sudo iptables-save > /etc/iptables/rules.v4

Understanding default policies:

  • -P INPUT ACCEPT - Sets default policy for incoming traffic
  • -P OUTPUT ACCEPT - Sets default policy for outgoing traffic
  • -P FORWARD ACCEPT - Sets default policy for forwarded traffic (crucial for gateway)
  • iptables-save - Exports current rules to a file

⚠️ Security Note: This configuration opens all ports. For production environments, implement more restrictive rules:

bash
1# Alternative: Restrictive approach with specific allowances
2sudo iptables -P INPUT DROP
3sudo iptables -P FORWARD DROP
4sudo iptables -P OUTPUT ACCEPT
5
6# Allow SSH access (replace 22 with your SSH port)
7sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
8
9# Allow established connections
10sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
11
12# Allow loopback
13sudo iptables -A INPUT -i lo -j ACCEPT
14
15# Allow forwarding from specific subnet
16sudo iptables -A FORWARD -s 192.168.1.0/24 -j ACCEPT
17sudo iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
bash
1sudo ufw disable
2sudo ufw default allow incoming
3sudo ufw default allow outgoing
4sudo ufw enable

UFW (Uncomplicated Firewall) management:

  • Provides a simpler interface over iptables
  • Default policies set before enabling to avoid lockout
  • More user-friendly for basic firewall management

Troubleshooting UFW:

bash
1# Check UFW status and rules
2sudo ufw status numbered
3
4# Delete specific rule by number
5sudo ufw delete <number>
6
7# Reset UFW to default state
8sudo ufw reset
9
10# Check UFW logs
11sudo tail -f /var/log/ufw.log

OpenVPN

OpenVPN is the open-source VPN protocol used by ProtonVPN. We need to install the client and configure DNS resolution.

bash
1sudo apt install openvpn
2sudo apt install openresolv
3sudo wget "https://raw.githubusercontent.com/ProtonVPN/scripts/master/update-resolv-conf.sh" -O "/etc/openvpn/update-resolv-conf"
4sudo chmod +x "/etc/openvpn/update-resolv-conf"

Component breakdown:

  • openvpn - Main VPN client software
  • openresolv - DNS management framework for OpenVPN
  • update-resolv-conf - Script that updates DNS settings when VPN connects/disconnects

Why update-resolv-conf is important: This script ensures DNS queries are routed through the VPN provider's DNS servers, preventing DNS leaks. It's called automatically by OpenVPN through up and down script directives.

Verify installation:

bash
1# Check OpenVPN version
2openvpn --version
3
4# Verify script is executable
5ls -la /etc/openvpn/update-resolv-conf
6
7# Test DNS resolution after VPN connection
8nslookup google.com
9dig google.com

OpenVPN config

The configuration file tells OpenVPN how to connect to ProtonVPN servers. Download your .ovpn file from the ProtonVPN dashboard.

bash
1sudo mv /path/to/your/file.ovpn /etc/openvpn/client.conf
2sudo systemctl enable [email protected]
3sudo systemctl start [email protected]
4sudo systemctl status [email protected]

Understanding systemd service naming:

  • [email protected] - The @client part matches client.conf filename
  • If you named it proton.conf, the service would be [email protected]
  • enable - Makes the service start automatically on boot
  • start - Starts the service immediately
  • status - Shows current status and recent log entries

Additional systemd commands:

bash
1# Stop the VPN connection
2sudo systemctl stop [email protected]
3
4# Restart the VPN connection
5sudo systemctl restart [email protected]
6
7# View detailed logs
8sudo journalctl -u [email protected] -f
9
10# Check if service is set to start on boot
11sudo systemctl is-enabled [email protected]

Then, modify the .ovpn file (/etc/openvpn/client.conf) to include this line:

bash
1sudo nano /etc/openvpn/credentials.txt
auth-user-pass /etc/openvpn/credentials.txt

Credentials file format: Create /etc/openvpn/credentials.txt with two lines:

your-protonvpn-username your-protonvpn-password

Security best practices:

bash
1# Set restrictive permissions (only root can read)
2sudo chmod 600 /etc/openvpn/credentials.txt
3sudo chown root:root /etc/openvpn/credentials.txt
4
5# Verify permissions
6ls -la /etc/openvpn/credentials.txt
7# Should show: -rw------- 1 root root

Additional client.conf modifications: Add these lines to /etc/openvpn/client.conf for better functionality:

bash
1# DNS leak protection (use ProtonVPN's DNS)
2script-security 2
3up /etc/openvpn/update-resolv-conf
4down /etc/openvpn/update-resolv-conf
5
6# Automatically reconnect on connection drop
7persist-key
8persist-tun
9keepalive 10 60
10
11# Log file for troubleshooting
12log-append /var/log/openvpn-client.log
13verb 3

Monitor the log file:

bash
1# Watch logs in real-time
2sudo tail -f /var/log/openvpn-client.log
3
4# Check for connection errors
5sudo grep -i error /var/log/openvpn-client.log

Test

bash
1curl ip.me

Comprehensive testing suite:

1. Verify VPN connection is active:

bash
1# Check tun0 interface exists
2ip addr show tun0
3
4# Should show the VPN tunnel interface with an IP address
5# Example output: inet 10.x.x.x/xx

2. Check your public IP (should show VPN exit server IP):

bash
1# Multiple services to verify IP
2curl ifconfig.me
3curl icanhazip.com
4curl ipinfo.io/ip
5
6# Get detailed info including location
7curl ipinfo.io

3. DNS leak test:

bash
1# Check which DNS servers you're using
2nslookup -type=txt whoami.akamai.net
3
4# Or using dig
5dig +short TXT whoami.akamai.net
6
7# Should show ProtonVPN's DNS servers, not your ISP's
8cat /etc/resolv.conf

4. Verify routing through VPN:

bash
1# Show routing table
2ip route show
3
4# Default route should go through tun0
5# Example: default via 10.x.x.x dev tun0
6
7# Traceroute to see the path
8traceroute -n google.com
9# First hops should be VPN infrastructure

5. Test from another device: If you've configured other devices to use this VM as gateway:

bash
1# From another device on your network, set gateway to this VM's IP
2# Then test:
3curl ipinfo.io
4
5# Should show the VPN exit IP, not your real IP

6. Network performance test:

bash
1# Install speedtest-cli if needed
2sudo apt install speedtest-cli
3
4# Test VPN connection speed
5speedtest-cli
6
7# Ping test
8ping -c 4 8.8.8.8

Troubleshooting

VPN won't connect:

bash
1# Check OpenVPN service status
2sudo systemctl status [email protected]
3
4# View detailed logs
5sudo journalctl -xeu [email protected]
6
7# Test configuration manually (foreground mode)
8sudo openvpn --config /etc/openvpn/client.conf

DNS not working:

bash
1# Check DNS resolution
2systemd-resolve --status
3
4# Verify update-resolv-conf script executed
5cat /etc/resolv.conf
6
7# Manually test DNS
8nslookup google.com 8.8.8.8

Routing issues:

bash
1# Verify IP forwarding is enabled
2sysctl net.ipv4.ip_forward
3
4# Check NAT rules are active
5sudo iptables -t nat -L -n -v
6
7# Verify firewall isn't blocking
8sudo iptables -L -n -v
9
10# Check interface status
11ip link show

Connection drops frequently:

bash
1# Add to client.conf
2resolv-retry infinite
3nobind
4persist-key
5persist-tun
6keepalive 10 120
7
8# Then restart service
9sudo systemctl restart [email protected]

Advanced Configuration

Kill switch (prevent traffic without VPN):

bash
1# Drop all traffic not going through VPN
2sudo iptables -A OUTPUT ! -o tun0 -m owner ! --uid-owner root -j DROP
3
4# Allow local network
5sudo iptables -I OUTPUT -o eth0 -d 192.168.0.0/16 -j ACCEPT

Split tunneling (route only specific traffic through VPN):

bash
1# Create routing table
2echo "200 vpn" | sudo tee -a /etc/iproute2/rt_tables
3
4# Route specific subnet through VPN
5sudo ip rule add from 192.168.100.0/24 table vpn
6sudo ip route add default via 10.x.x.x dev tun0 table vpn

Monitor VPN status with script:

bash
1#!/bin/bash
2# save as /usr/local/bin/vpn-status.sh
3
4VPN_IP=$(curl -s ifconfig.me)
5TUN0_STATUS=$(ip addr show tun0 2>/dev/null | grep -q "inet" && echo "UP" || echo "DOWN")
6
7echo "VPN Status: $TUN0_STATUS"
8echo "Public IP: $VPN_IP"
9echo "Timestamp: $(date)"

Make it executable and add to cron:

bash
1sudo chmod +x /usr/local/bin/vpn-status.sh
2# Run every 5 minutes
3echo "*/5 * * * * /usr/local/bin/vpn-status.sh >> /var/log/vpn-monitor.log" | sudo crontab -