-
Notifications
You must be signed in to change notification settings - Fork 19
Architecture and Internals
This page describes how the container works internally. Useful for contributors, debugging, and understanding the boot sequence.
The container uses s6-overlay for process supervision. Services start in a defined order:
entrypoint (container start)
│
├─ init-adduser Create nordvpn user/group
├─ init-createauth Write credentials file (0600, owned by nordvpn)
├─ init-createmgmtpwfile Generate management interface password
├─ init-firewall Apply iptables rules (depends on entrypoint backend selection)
├─ init-setupcron Configure cron jobs from RECREATE_VPN_CRON / CHECK_CONNECTION_CRON
│
├─ svc-nordvpn Main OpenVPN service (long-running)
└─ svc-cron Cron daemon (long-running)
Location: /usr/local/bin/entrypoint
- Detects kernel version
- Tests nft and legacy iptables backends by toggling a chain policy (
DROP↔ACCEPTonOUTPUT) - If preferred backend fails, tests the fallback
- If legacy is selected and nft tables already contain rules, flushes nft tables to avoid mixed stacks
- Exports selected backends (
IPT,IP6T) to/run/xt/backend.env - Sets
INPUT,OUTPUT,FORWARDtoDROPon both IPv4 and IPv6 - Allows loopback traffic
Backend preference by kernel:
| Kernel version | Preferred backend | Fallback |
|---|---|---|
| ≥ 4.18 | nft (iptables) |
legacy (iptables-legacy) |
| < 4.18 | legacy (iptables-legacy) |
nft (iptables) |
Location: /usr/local/bin/backend-functions
Sourced by every script. Provides:
-
run4()/run6()— Execute iptables commands with logging (non-fatal) -
run4_critical()/run6_critical()— Execute or sleep forever on failure -
is_vpn_connected()— Checks for tun0 interface -
mgmt_cmd()— Sends authenticated commands to OpenVPN management interface -
log()/log_error()/log_warning()— Timestamped logging -
parse_cron()— Converts cron expressions to human-readable descriptions
Location: /usr/local/bin/vpn-config
- Resolves COUNTRY/CITY/GROUP/TECHNOLOGY to numeric IDs using JSON data files
- Builds NordVPN API query URL
- Fetches server list using pinned API IPs (no DNS)
- Detects specific server hostnames and gives them
load=0 - Sorts by load (multi-location) or keeps API order (single location)
- Applies
RANDOM_TOPif set - Writes selected server's OpenVPN config to disk
For XOR technologies (openvpn_xor_*), the script also:
- Generates multiple
remotelines from the XOR port list (withremote-random) - Adds the
scramble obfuscatedirective with the XOR pre-shared key - Swaps the
<tls-auth>block with the XOR-specific key fromtls-auth-xor.pem
Location: /etc/s6-overlay/s6-rc.d/svc-nordvpn/run
- Calls
vpn-configto get server configuration - Extracts VPN server IP/port/protocol from the OVPN file
- Adds temporary firewall rules in
VPN-SERVERchain for everyremoteline (XOR configs have multiple ports) - Appends
--data-ciphersif not already set - Launches OpenVPN with auth, management port, and nordvpn group
- Waits for tun0 interface (up to 60 seconds)
- Optionally runs network diagnostics
- Blocks on OpenVPN process
Location: /etc/s6-overlay/s6-rc.d/svc-nordvpn/finish
- Flushes
VPN-SERVERiptables chain - Sends SIGTERM via management interface (5-second timeout)
- Removes OVPN config file
Location: /usr/local/bin/vpn-healthcheck
- Sends HTTP HEAD requests to configured URL(s)
- Retries
CHECK_CONNECTION_ATTEMPTStimes with configurable interval - If all fail, calls
vpn-reconnect
Location: /usr/local/bin/vpn-reconnect
- Stops
svc-nordvpnvia s6-rc - Waits 2 seconds
- Restarts
svc-nordvpn
Location: /usr/local/bin/network-diagnostic
Two modes:
-
--basic: Public IP + geolocation only -
--full(default): Complete diagnostics including interfaces, iptables rules, DNS, routes, OpenVPN status
Located in /usr/local/share/nordvpn/data/:
| File | Purpose |
|---|---|
countries.json |
Country name/code/ID mappings |
groups.json |
Server group definitions |
technologies.json |
VPN technology definitions |
template.ovpn |
Base OpenVPN configuration template |
tls-auth-xor.pem |
XOR-specific TLS pre-shared key (swapped in at runtime for XOR technologies) |
Origin of these files: They are generated from NordVPN's own published material, not maintained by hand. The JSON files (
countries.json,groups.json,technologies.json) come from the NordVPN public API (https://api.nordvpn.com/), andtemplate.ovpn/tls-auth-xor.pemare derived from the official OpenVPN configuration files NordVPN distributes (https://downloads.nordcdn.com/configs/). The.ovpntemplate keeps NordVPN's settings, embedded CA certificate andtls-authstatic key, with placeholders (__PROTOCOL__,__REMOTES__,__SCRAMBLE__,__X509_NAME__) thatvpn-configsubstitutes at runtime. All of these are refreshed automatically by themaintenance-updatesGitHub Actions workflow, which opens a pull request when NordVPN changes its certificates, static key, API schema, or recommended options. Do not edit them by hand — manual changes are overwritten the next time the workflow runs.
| Path | Purpose |
|---|---|
/run/xt/backend.env |
Selected iptables backend (IPT, IP6T) |
/run/xt/nordvpn.ovpn |
Current server's OpenVPN config |
/run/xt/auth |
Credentials file (0600) |
/run/xt/mgmt-pw |
Management interface password |
The container runs an OpenVPN management interface for status queries and graceful shutdown:
| Setting | Value |
|---|---|
| Host | 127.0.0.1 |
| Port | 7505 |
| Auth | Password from /run/xt/mgmt-pw
|
Used internally by:
-
vpn-healthcheck— queries connection state -
network-diagnostic— fetches uptime, remote IP, protocol details -
svc-nordvpn/finish— sends SIGTERM for graceful shutdown
Authentication flow: the mgmt_cmd() function connects via netcat, sends the password, waits for the prompt, sends the command, then quits.
The entrypoint script runs first and:
- Selects the iptables backend (nft or legacy — see Firewall Backends)
- Sets
INPUT,OUTPUT, andFORWARDpolicies toDROPon both IPv4 and IPv6 - Allows loopback traffic (required for inter-process communication)
At this point, all network traffic is blocked.
The init-firewall service then:
- Detects the Docker network (eth0 subnet and gateway)
- Enables connection tracking (ESTABLISHED/RELATED)
- Sets up MASQUERADE on the tun0 (VPN) interface
- Creates a
VPN-SERVERchain for temporary per-connection rules - Adds NordVPN API IP exceptions (TCP/443 only) from
NORDVPNAPI_IP - If
NETWORKis set, adds static routes and bidirectional allow rules for those CIDRs
When OpenVPN connects:
- The VPN server IP/port gets a temporary rule in the
VPN-SERVERchain - For XOR technologies, multiple rules are added (one per
remoteport — see Technologies for port lists) - When the connection drops,
svc-nordvpn/finishflushes that chain
INPUT chain: ACCEPT lo → ACCEPT ESTABLISHED,RELATED → [NETWORK CIDRs] → DROP
OUTPUT chain: ACCEPT lo → ACCEPT ESTABLISHED,RELATED → VPN-SERVER → [NETWORK CIDRs] → ACCEPT tun0 → [NORDVPNAPI IPs] → DROP
FORWARD chain: ACCEPT ESTABLISHED,RELATED → ACCEPT tun0 → DROP
VPN-SERVER chain: [temporary rules for current VPN server IP:port(s) — one rule per remote line]
NAT/POSTROUTING: MASQUERADE on tun0
Configuration
- Server Selection
- Server Groups
- Technologies
- IPv6 Configuration
- Automatic Reconnection
- Local Network Access
- VPN Gateway Mode
- OpenVPN Options
- Custom DNS
- Permissions
Security
Examples
Operations
Reference