Symptom: You have a valid Let’s Encrypt certificate installed on your Nginx/Virtualmin server, but browsers show a self-signed certificate warning instead. The Nginx configuration looks correct, and openssl s_client from the server itself returns the right certificate — but external connections return the wrong one.
If OpenVPN Access Server was installed on your server before Nginx/Virtualmin, this article is for you.
The Root Cause
OpenVPN Access Server installs its own nftables rules during setup, one of which DNATs (redirects) all incoming TCP traffic on port 443 to its internal VPN daemon port. This rule persists even after Nginx is installed and configured — silently intercepting every external HTTPS connection before Nginx ever sees it.
The deceptive part: because the DNAT rule lives in nftables (not iptables), a standard iptables -L check returns nothing, making the interception invisible to most troubleshooting workflows.
What the traffic flow looks like
# What you expect:
Browser → port 443 → Nginx → Let's Encrypt cert ✓
# What actually happens:
Browser → port 443 → nftables DNAT → OpenVPN daemon:916 → Self-signed cert ✗
Why openssl works from the server itself
When running openssl s_client from the server, the connection goes through the OUTPUT chain, bypassing the PREROUTING DNAT rule. So local tests always show the correct certificate while external clients see the wrong one.
Diagnosis
Step 1 — Confirm the symptom
From an external machine, test what certificate is actually being served:
echo | openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null \
| openssl x509 -noout -issuer -subject
If the issuer shows something other than Let’s Encrypt (e.g. an OpenVPN CA), the connection is being intercepted.
Step 2 — Check nftables for the DNAT rule
nft list ruleset | grep 443
Look for a line like:
ip daddr YOUR.SERVER.IP tcp dport 443 ct state new jump AS_DPFWD_TCP
And the corresponding chain:
chain AS_DPFWD_TCP {
ip protocol tcp dnat to YOUR.SERVER.IP:916
}
This confirms OpenVPN AS is redirecting all external port 443 TCP traffic to its own daemon.
Step 3 — Check OpenVPN AS port configuration
/usr/local/openvpn_as/scripts/sacli ConfigQuery | grep -E "port|proto|daemon"
If you see "vpn.server.daemon.tcp.port": "443", the VPN daemon is configured to listen on port 443 — which is what created the nftables rule in the first place.
The Fix
Move the OpenVPN TCP daemon off port 443. Port 1194 is OpenVPN’s default and standard port.
/usr/local/openvpn_as/scripts/sacli --key "vpn.server.daemon.tcp.port" --value "1194" ConfigPut
/usr/local/openvpn_as/scripts/sacli --key "vpn.daemon.0.listen.port" --value "1194" ConfigPut
/usr/local/openvpn_as/scripts/sacli start
OpenVPN AS will automatically update its nftables rules and remove the port 443 interception.
Verification
Confirm the DNAT rule is gone
nft list ruleset | grep 443
Should return no output.
Confirm the correct certificate is now served externally
echo | openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null \
| openssl x509 -noout -issuer -subject
Should now return your Let’s Encrypt certificate.
OpenVPN Clients
If your VPN clients were connecting over TCP 443, download fresh .ovpn profiles from the OpenVPN AS web UI (available at https://yourserver:943) — they will automatically reflect the updated port.
Clients connecting over UDP 1194 (the default for most configurations) are unaffected and require no changes.
Note: To access the OpenVPN AS web UI on port 943, ensure your firewall allows it:
firewall-cmd --permanent --add-port=943/tcp firewall-cmd --reload
Why This Happens
OpenVPN Access Server is designed to run on port 443 by default to allow VPN connections through restrictive firewalls that only permit HTTPS traffic. When installed first, it claims port 443 at the nftables level. A subsequent Nginx installation has no mechanism to detect or override this — Nginx binds its listener correctly, but never receives the external traffic.
The use of nftables (rather than iptables) means the interception is invisible to iptables -L checks, which is a common first step in port conflict troubleshooting.
Summary
| Check | Command |
|---|---|
| Confirm external cert | openssl s_client -connect domain:443 -servername domain |
| Find nftables DNAT rule | nft list ruleset | grep 443 |
| Check OpenVPN port config | sacli ConfigQuery | grep port |
| Move OpenVPN off 443 | sacli --key "vpn.server.daemon.tcp.port" --value "1194" ConfigPut |
| Verify fix | nft list ruleset | grep 443 (should be empty) |