A practical guide to turning a provider-supplied .ovpn file into a hardened connection, protected by a firewall kill switch, and verified against IP and DNS leaks.


Introduction🔗

The .ovpn configuration files shipped by commercial VPN providers work, but they often prioritize maximum compatibility over security. You’ll routinely find deprecated compression, cipher suites with no forward secrecy, and no protection against leaks when the tunnel drops.

This article walks through, step by step, the hardening of a real IPVanish configuration: cleaning up the .ovpn file, setting up a kill switch with UFW, then concretely verifying that the IP address (including DNS queries) never leaks.

The guiding principle: only harden what is testable, and verify every change, because a directive that’s too strict on the client side can break the connection if the server doesn’t follow.


1. Hardening the OpenVPN Configuration🔗

The starting point🔗

A typical IPVanish configuration contains several security-questionable elements:

  • comp-lzo: compression applied before encryption;
  • a tls-cipher list limited to CBC-SHA suites, some of them without forward secrecy;
  • no remote-cert-tls server;
  • no tls-version-min;
  • AES-256-CBC present in the data channel.

The fixes, by priority🔗

Remove compression (critical). Compression applied before encryption exposes you to the VORACLE attack (2018), which allows recovering plaintext content such as cookies or tokens. This is why compression has been deprecated since OpenVPN 2.5.

# Remove:
comp-lzo

# Add:
allow-compression no

Enforce a server certificate. verify-x509-name checks the certificate name, but without remote-cert-tls server, another client signed by the same provider CA could theoretically impersonate the server.

remote-cert-tls server

Block TLS downgrade. With no version floor, negotiation down to TLS 1.0/1.1 remains possible.

tls-version-min 1.2

Favor modern TLS suites. The TLS-RSA-WITH-... suites offer no forward secrecy: if the server’s private key ever leaks, all recorded past traffic becomes decryptable. Prefer ECDHE + AEAD (GCM) suites.

tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384

Prefer AEAD on the data channel. GCM is preferable to CBC. If the server supports GCM only, you can drop CBC from the main list while keeping a fallback.

data-ciphers AES-256-GCM
data-ciphers-fallback AES-256-CBC

Complete hardened configuration🔗

client
dev tun
proto udp
remote dxb-c22.ipvanish.com 1194
resolv-retry infinite
nobind
persist-key
persist-tun
persist-remote-ip

# --- Server verification (hardened) ---
verify-x509-name dxb-c22.ipvanish.com name
remote-cert-tls server          # require the peer to be a "server" cert

auth-user-pass

# --- Compression disabled (VORACLE mitigation) ---
allow-compression no            # replaces comp-lzo

verb 3

# --- Data channel ---
auth SHA256
data-ciphers AES-256-GCM        # AEAD; CBC fallback kept
data-ciphers-fallback AES-256-CBC

# --- Control channel (TLS) hardened ---
tls-version-min 1.2             # block TLS 1.0/1.1 downgrade
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384

setenv CLIENT_CERT 0
<ca>
...
</ca>

Testing method: one directive at a time🔗

Each hardened directive can break the connection if the server doesn’t follow. So add them one by one, connect, and read the log (verb 3 is enough; verb 4 to debug a handshake). Suggested order, from safest to riskiest:

OrderDirectiveRisk of failureSymptom on failure
1allow-compression noNear zero (client-side)n/a
2remote-cert-tls serverLowVERIFY EKU ERROR
3tls-version-min 1.2LowTLS handshake error
4tls-cipher ...ECDHE...HighestTLS error / handshake failed
5data-ciphers AES-256-GCMMediumcipher negotiation warning

A successful handshake shows something like this in the log:

Control Channel: TLSv1.2, cipher TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384
Data Channel: cipher AES-256-GCM

What you CANNOT add unilaterally🔗

tls-crypt / tls-auth would add an “HMAC firewall” on the control channel (anti-scan/DoS protection and protocol obfuscation). But they require a shared static key provided by the provider inside the file (<tls-crypt> or <tls-auth> block). You can’t invent it on the client side: without the server’s matching key, the handshake is rejected. Check the IPVanish portal to see whether they offer tls-crypt configs.


2. The Kill Switch with UFW🔗

The principle🔗

A kill switch rests on a simple idea: block everything by default, then allow only two things: the initial connection to the VPN server (the handshake) and all traffic going through the tun interface. If the tunnel drops, no allowed route remains, so nothing leaks.

Set this up before connecting to the VPN.

Command line🔗

1. Default policies. The deny outgoing is the heart of the setup: without it, this isn’t a real kill switch.

sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default deny outgoing

2. Loopback.

sudo ufw allow in on lo
sudo ufw allow out on lo

3. Connection to the VPN server. This is the only traffic allowed on the physical interface. Allowing by port is robust because the server IP can change:

sudo ufw allow out to any port 1194 proto udp

Stricter variant, pinning the IP (redo if it changes; IPVanish rotates addresses often):

dig +short dxb-c22.ipvanish.com
sudo ufw allow out to <SERVER_IP> port 1194 proto udp

4. All traffic on the tunnel.

sudo ufw allow out on tun0

5. Enable and verify.

sudo ufw enable
sudo ufw status verbose

The tun0 rule: the sticking point🔗

Two difficulties consistently come up with this rule, especially through the Gufw graphical interface:

  • tun0 only exists when the VPN is connected. As long as you’re not connected, the interface doesn’t appear in Gufw’s lists.
  • Gufw refuses a “bare” rule. In the Advanced tab, it often requires a port or protocol and rejects a rule that targets only an interface.

But the “allow all outgoing traffic on tun0” rule specifically has no port: it must let everything through (443 for the web, 53 for DNS, etc.). Adding a random port would break the kill switch.

The reliable solution is the terminal, which accepts the interface name as text, whether the VPN is connected or not:

sudo ufw allow out on tun0

The rule is written even if tun0 doesn’t exist yet; it will become active as soon as the tunnel comes up. It then appears normally in Gufw. Verification:

sudo ufw status verbose
# Expected: Anywhere on tun0    ALLOW OUT    Anywhere

Watch the interface name. It’s almost always tun0, but sometimes tun1 (NetworkManager, multiple VPNs, leftover WireGuard). If the kill switch blocks everything while the tunnel is clearly up, check with ip a that the real name matches the rule.

Gufw profiles vs. CLI rules: beware of inconsistencies🔗

UFW has no real notion of “profiles”: Gufw’s profiles (Home, Public, Office
) are a layer added by Gufw, which rewrites the active UFW config on every profile switch.

Consequence: a rule added via CLI applies to the profile active at the time of the command, not to all of them. When you switch profiles, Gufw reloads its own rule set and the tun0 rule may not be present, or may even be overwritten, depending on the version.

Recommendations:

  • If the VPN kill switch must be always active, it’s better not to rely on Gufw profiles and to manage everything via CLI (or a script triggered on VPN connection).
  • If you rely on profiles for other uses, add the tun0 rule in each relevant profile: select the profile, close Gufw, run sudo ufw allow out on tun0, reopen, and verify.
  • Always check after a profile switch with sudo ufw status verbose.

Local LAN (optional)🔗

To keep access to the local network (printer, NAS), which creates a small exception to the kill switch:

sudo ufw allow out to 192.168.0.0/16
sudo ufw allow in to 192.168.0.0/16

Troubleshooting: “Cannot resolve host address”🔗

The most common failure right after enabling the kill switch is that the VPN won’t connect at all, with a log line like this:

RESOLVE: Cannot resolve host address: yul-c67.ipvanish.com:1194 (Name or service not known)

This is not a VPN failure. It happens before the tunnel. OpenVPN must resolve the server hostname to connect. That DNS query leaves on port 53 through the physical interface, and the deny outgoing policy blocks it. No resolution, no connection: the tunnel never starts.

A useful clarification while we’re here: a allow out on tun0 rule can never intercept this handshake, even if it appears first in the list. UFW evaluates rules in order, but only among those whose selector matches the packet. The handshake leaves on the physical interface toward port 1194; the on tun0 rule only matches traffic on tun0, which doesn’t exist yet. The two rules don’t overlap, so their order is irrelevant to connecting. If you can’t connect, look at DNS resolution first, not rule order.

Two ways to fix it.

Solution 1 (recommended): pin the IP, remove the DNS dependency. Resolve the hostname once with the firewall down, then work with the IP.

sudo ufw disable
dig +short yul-c67.ipvanish.com
sudo ufw enable

# allow the resolved IP
sudo ufw allow out to <RESOLVED_IP> port 1194 proto udp

Then replace the hostname with the IP in the config (remote <IP> 1194, or the “Gateway” field in NetworkManager). No DNS resolution is needed to connect anymore, so the block disappears. Downside: IPVanish rotates its IPs, so redo this if the server changes address. This is the cleanest option for a strict kill switch, because it eliminates the pre-connection DNS leak instead of allowing it.

Solution 2: allow outbound DNS. If you’d rather keep the hostname, let resolution through:

sudo ufw allow out to any port 53 proto udp
sudo ufw allow out to any port 53 proto tcp

Simpler, but less strict: it opens a small hole in the kill switch (DNS queries can leave outside the tunnel before connecting). To tighten it, restrict to your resolver’s IP instead of any. With systemd-resolved, check resolvectl status to find the DNS actually in use, and allow only that address.

NetworkManager caveat. NetworkManager manages some routes and VPN-pushed DNS itself, which complicates the kill switch. After connecting with Solution 1, re-run an ipleak.net test to confirm no residual DNS leak remains once the tunnel is up. This is the classic weak spot with NetworkManager.


3. Verifying the IP (and DNS) Doesn’t Leak🔗

Config hardening and the kill switch are useless without concrete verification. Two leak categories: the real IP exposed if the tunnel drops, and DNS queries leaving outside the tunnel even while it’s active.

Testing the kill switch (IP leak)🔗

The decisive test: cut the VPN on purpose and confirm no internet access remains.

# 1. Connected to the VPN, note the public IP (should be IPVanish's)
curl ifconfig.me

# 2. Kill OpenVPN
sudo killall openvpn

# 3. Retry, MUST fail (timeout), not reveal the real IP
curl ifconfig.me

If the second command times out instead of returning the real ISP IP, the kill switch works.

Testing DNS leaks🔗

This is the trickiest point, and the #1 source of residual leaks even with an active kill switch. Traffic can go through the tunnel while DNS queries leave toward the ISP’s resolver.

Since outgoing is denied by default, DNS queries can only exit through tun0 (the intended behavior). If local resolution goes through systemd-resolved on 127.0.0.53, the loopback rule handles it and DNS goes through the tunnel.

Online checks, once connected:

  • ipleak.net: shows the visible public IP, the addresses of the DNS resolvers in use, and detects WebRTC leaks.
  • dnsleaktest.com: the extended test lists the DNS servers actually queried. They must all belong to the VPN, never the ISP.

Edge case worth knowing: if resolving the hostname dxb-c22.ipvanish.com fails before connecting (because the usual DNS is blocked by the kill switch), two options: pin the server IP in the firewall rule, or temporarily allow DNS to the resolver just long enough to establish the connection.

Additional anti-leak hardening🔗

These protections aren’t set in the .ovpn file alone:

  • block-outside-dns: on Windows, essential to prevent bypassing the tunnel’s DNS. (No effect on Linux.)
  • Firewall kill switch: covered in section 2; it’s what prevents the IP leak when the tunnel drops.
  • WebRTC: should be disabled or filtered on the browser side, as it can reveal the real IP independently of the VPN.

4. Reading the Logs to Debug🔗

When a hardened directive or a firewall rule breaks the connection, the log tells you exactly where. How you read it depends on how OpenVPN is launched.

Client launched manually (terminal)🔗

The log scrolls straight into the terminal where you ran the command. That is your real-time client log. To keep it in a file, add --log:

sudo openvpn --config /etc/openvpn/dxb-c22.ovpn --log /var/log/openvpn-client.log

Then follow it live from another terminal:

sudo tail -f /var/log/openvpn-client.log

Or set it once in the .ovpn so you don’t have to think about it:

log-append /var/log/openvpn-client.log
verb 3

Client running as a systemd service🔗

Use journalctl. The unit is often openvpn@<config-name>:

# find the exact unit name
systemctl list-units --type=service | grep -i openvpn

# follow live (like tail -f)
sudo journalctl -u openvpn@dxb-c22 -f

# since the last boot only
sudo journalctl -u openvpn@dxb-c22 -b

Client via NetworkManager🔗

When the VPN is configured in the network UI, the OpenVPN process runs as nm-openvpn under NetworkManager, so its output lands in the NetworkManager journal:

# follow VPN-related lines live
sudo journalctl -u NetworkManager -f | grep -iE 'vpn|openvpn'

# full history for a session
sudo journalctl -u NetworkManager --since "10 min ago"

A NetworkManager VPN log looks like this (note the nm-openvpn[pid] prefix), and this exact excerpt shows the DNS-block failure from section 2:

NetworkManager[1961]: vpn[...,"ipvanish-CA-Montreal-yul-c67"]: starting openvpn
nm-openvpn[17906]: OpenVPN 2.6.19 x86_64-pc-linux-gnu ...
nm-openvpn[17906]: RESOLVE: Cannot resolve host address: yul-c67.ipvanish.com:1194 (Name or service not known)

That last line = DNS resolution blocked by the kill switch, before the tunnel. Apply Solution 1 or 2 from section 2.

To raise NetworkManager’s own VPN verbosity for deeper debugging:

sudo nmcli general logging level DEBUG domains VPN
# revert afterwards:
sudo nmcli general logging level INFO domains DEFAULT

Turning up OpenVPN verbosity🔗

To debug a failing handshake (useful since we’re hardening the config), raise the level in the .ovpn:

verb 4

verb 3 is fine for normal use; verb 4 shows the TLS negotiation and cipher details; verb 5+ gets very noisy (low-level network debugging).

What to look for🔗

A successful client connection shows, in order:

TLS: Initial packet from [server]
VERIFY OK: depth=1 ... CN=...
Control Channel: TLSv1.2, cipher TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384
Data Channel: cipher AES-256-GCM
Initialization Sequence Completed

Initialization Sequence Completed = the tunnel is up. Common failures and their meaning:

Log lineCauseFix
RESOLVE: Cannot resolve host addressDNS blocked by kill switch before connectingPin the IP or allow port 53 (section 2)
TLS Error / handshake failedServer rejected a hardened TLS directiveLoosen tls-cipher / tls-version-min (section 1)
VERIFY EKU ERRORCert lacks the server EKU attributeRemove remote-cert-tls server
Connection timed outFirewall blocks the 1194/UDP egress, or wrong interface in the ruleRecheck the port/IP rule and interface name

Summary🔗

StepActionVerification
OpenVPN configRemove comp-lzo, add allow-compression no, remote-cert-tls server, tls-version-min 1.2, ECDHE-GCM suites, data-ciphers AES-256-GCMLog: Control Channel in ECDHE-GCM, Data Channel in AES-256-GCM
Firewalldeny incoming/outgoing by default + allow lo, port 1194/UDP, and sudo ufw allow out on tun0sudo ufw status verbose
IP anti-leakActive kill switchkillall openvpn then curl ifconfig.me → timeout
DNS anti-leakDNS traffic confined to the tunnelipleak.net / dnsleaktest.com → VPN resolvers only
Pre-connection DNSPin the server IP, or allow out ... port 53No more RESOLVE: Cannot resolve host in the log
DebuggingRead the log (terminal / journalctl -u openvpn@
 / journalctl -u NetworkManager)Initialization Sequence Completed

Three principles to remember: only harden what is testable (the provider’s server imposes its limits), verify every change individually, and never trust the config without testing real leaks. That’s the only true judge.


Note: the available TLS suites and the use of tls-crypt depend on what the IPVanish server accepts. Any hardened client-side directive that the server doesn’t follow will break the connection, hence the importance of incremental testing.