nns-app runs selected Linux applications in dedicated network namespaces and
connects each namespace through OpenVPN or WireGuard without replacing the
host's default route or DNS configuration.
Release: 1.3.23
Supported platforms: Ubuntu and Fedora with systemd
VPN backends: OpenVPN 2.6+, WireGuard, and inherit-only child namespaces
The release includes both the modular source tree and a pre-built,
single-file installer: nns-app-install.sh.
Install nns-app once on the local Linux host:
chmod +x nns-app-install.sh
sudo ./nns-app-install.sh installThen choose one of the following two alternatives. Use a different application name if you want to keep both configurations on the same client.
Install the application environment, then add either your own profile:
nns-app install my-private-app
nns-app add my-private-app ~/my-base-profile.ovpn
nns-app run my-private-app ping -c 4 1.1.1.1or let nns-app select and test a free VPN Gate profile, optionally by country:
nns-app install my-private-app
nns-app add my-private-app any
# Or, for example: nns-app add my-private-app any JP
nns-app run my-private-app ping -c 4 1.1.1.1The selected provider profile runs on the current host. The ping process runs
inside my-private-app and its traffic exits through that profile.
Use your own profile:
nns-app install my-private-app via --remote user@remote-host
nns-app add my-private-app ~/my-base-profile.ovpn
nns-app run my-private-app ping -c 4 1.1.1.1or ask the remote host to download, rank, and probe a free VPN Gate profile, then deploy the selected relay automatically:
nns-app install my-private-app via --remote user@remote-host
nns-app add my-private-app any
# Or, for example: nns-app add my-private-app any JP
nns-app run my-private-app ping -c 4 1.1.1.1The first command bootstraps or upgrades nns-app on remote-host over SSH. For
add ... any, the VPN Gate relay list is downloaded and candidates are probed
on remote-host; the local client does not need access to VPN Gate or to the
candidate relay endpoints. The second command then automatically creates the
private remote exit, gateway, client credentials, and local link. The final command runs ping locally inside
my-private-app, with its traffic routed through the remote host and then
through the deployed provider VPN. Existing ssh user@remote-host access and
remote sudo permission are required for the first bootstrap.
VPN Gate is a volunteer-operated public network. Relays may disappear, log traffic, or provide inconsistent performance. Use end-to-end encryption and a provider-managed profile for reliable or sensitive use.
- One isolated network namespace per named application environment.
- OpenVPN and WireGuard client profiles.
- Per-environment DNS, routes, firewall state, tunnel state, and kill switch.
- Local VPN chaining through
--via <upstream-app>. - Inherit-only child namespaces that share one upstream tunnel without opening another VPN session.
- Detailed
statusoutput with focused failure logs. - Adaptive data-path watchdog that preserves running application namespaces.
- Snap desktop-application support inside private mount namespaces.
- Managed remote OpenVPN gateways routed through a selected remote nns-app exit.
- Three-command automatic remote deployment over SSH, with automatic sharing of matching provider exits plus manual enrollment, synchronization, credential rotation, and status.
- Direct, SSH-forward, stunnel, and Cloak gateway transports with portable
.nnslinkbundles. - Unique client certificates and TLS Crypt v2 keys for gateway clients.
- Collision-safe network, routing-table, and policy-priority allocation.
- Dedicated, tagged gateway firewall chains.
- Fedora firewalld integration with a private nns-app zone and forwarding policy.
- Cycle detection and systemd dependencies for chained environments.
- Weekly atomic CRL renewal for managed gateways.
- Transactional gateway, client, and PKI changes.
- Process-wide and per-object locking for administrative operations.
The examples use descriptive placeholders consistently:
| Name | Meaning |
|---|---|
my-private-app |
A local application environment with its own VPN profile |
my-upstream-vpn |
A local nns-app environment used as another environment's upstream |
my-base-profile.ovpn |
A provider OpenVPN profile imported into an nns-app environment |
my-app-profile.ovpn |
The profile used by a downstream application environment |
my-wireguard-profile.conf |
A provider WireGuard profile imported into an nns-app environment |
my-remote-exit |
The nns-app environment on the remote Linux box that provides egress |
my-relay |
The managed gateway exposed by the remote Linux box |
my-linux-client |
One unique gateway client identity for a local Linux box |
my-remote-profile.ovpn |
A direct self-contained profile exported by the remote gateway |
my-remote-link.nnslink |
A gateway profile plus transport metadata and pinned transport material |
my-remote-vpn |
The local nns-app environment that imports the remote profile |
Replace these names with labels that describe your own applications, profiles, servers, and clients.
Paths beginning with ~ refer to the invoking user's home directory. The shell
expands ~ before sudo starts nns-app, so a command such as
sudo nns-app add my-private-app ~/my-base-profile.ovpn reads the profile from
the current user's home, not from /root. Imported profiles are copied into
root-owned nns-app storage. Gateway exports are written with mode 0600 and,
when invoked through sudo, ownership is returned to the invoking user.
For the usual case—run a local application through a provider profile that must execute on a remote Linux host—the public workflow is only:
nns-app install my-private-app via --remote user@remote-host
nns-app add my-private-app ~/my-base-profile.ovpn
nns-app run my-private-app ping 1.1.1.1The compact --via-remote user@remote-host form remains accepted as an alias.
The first command uses the invoking user's existing SSH access. It confirms and
pins the host key, installs or upgrades the same nns-app version remotely,
creates a dedicated root-owned SSH key for subsequent unattended operation, and
installs a restricted remote helper. Remote sudo may request a password once
during this bootstrap.
The second command validates the self-contained provider profile locally and
then deploys it remotely. nns-app automatically creates a hidden remote exit,
a loopback-only managed OpenVPN gateway, a unique client credential, and the
local .nnslink profile. It also starts the local environment and enables both
ends for boot recovery. The default data transport is a supervised SSH local
forward through the existing SSH port, so no additional cloud firewall rule or
public gateway port is needed.
When another automatic client on the same remote host uses an identical recorded profile, nns-app reuses that provider exit immediately. If the profile files differ but the newly started provider session receives the same provider-side tunnel IPv4 address as an existing automatic exit, nns-app treats them as the same single-session provider identity. The address comparison runs as soon as the candidate tunnel address is assigned, before full Internet readiness is required, so a provider that drops the older duplicate session cannot trap deployment in a reconnect/timeout loop. nns-app removes the duplicate exit, reconnects the existing pool, and adds a new unique gateway client. The result is one provider VPN session and one remote gateway shared by several local nns-app environments, while each local machine keeps its own certificate, private key, TLS Crypt v2 key, SSH key, and lifecycle state.
The third command executes the requested process inside the already running
local namespace. If the environment was stopped manually, run starts it
again. Normal status, synchronization, and credential rotation remain
available:
nns-app status my-private-app
sudo nns-app remote status my-private-app
sudo nns-app remote sync my-private-app
sudo nns-app remote rotate my-private-appRequirements for automatic mode:
- use a new or otherwise unconfigured application name; nns-app will not silently convert an existing local-profile environment into a remote one;
ssh user@remote-hostmust already work for the invoking user, using a key, agent, or an interactive password during the first bootstrap;- the remote SSH endpoint must resolve to IPv4; nns-app's managed data plane is currently IPv4-only;
- that remote account must be allowed to run
sudoduring bootstrap; - provider OpenVPN profiles must contain inline credentials and must not need an interactive password, as in local nns-app mode.
Automatic-remote environments use AUTOSTART="on". After either client or
remote host reboots, systemd recreates the remote provider exit, private
gateway, local SSH forward, OpenVPN client, online check, and watchdog. Running
applications themselves do not survive a client reboot.
For a shared pool, nns-app stop <app> disconnects that local member and marks
it inactive. The remote provider exit remains online while another pool member
is active; the last active member stops the shared gateway and exit. start or
run reactivates the member and brings the pool online when necessary.
Removing an automatic-remote app revokes that client's remote gateway credential, removes its state and dedicated SSH key, and preserves a shared provider pool while other clients still reference it. The managed gateway and provider exit are removed only with the last pool member:
nns-app remove my-private-appnns-app purge performs the same ownership-scoped cleanup for every automatic
remote before deleting local nns-app state and binaries. If a remote host is
unreachable, removal fails before local credentials are deleted, so it can be
retried. Use --local-only only when intentionally abandoning remote objects:
sudo nns-app purge --local-onlyCleanup does not uninstall the shared nns-app engine or VPN packages from the remote host because other clients may still use them.
The lower-level remote, gateway, link, --transport, and manual export
commands remain supported for custom listeners, public direct gateways,
stunnel, Cloak, external PKI, or other fine tuning.
The repository already contains:
nns-app-install.sh
Install directly from it:
chmod +x nns-app-install.sh
sudo ./nns-app-install.sh installTo rebuild it from the modular source:
./build.shThe equivalent Make target is:
make buildThe build syntax-checks every ordered module under src/, concatenates the
modules, validates the combined Bash file, compiles every embedded Python
helper, and writes a deterministic nns-app-install.sh in the project root.
Run the static and helper tests as a regular user:
make testThe tests use private temporary directories and do not require root. Use root only for installation and for commands that change live networking, systemd, firewall, routing, or PKI state.
src/
00-preamble.sh metadata, help, common helpers, and locking
10-config.sh configuration loading and upstream-graph validation
20-install.sh dependencies, systemd units, and app provisioning
30-profiles.sh OpenVPN/WireGuard validation and VPN Gate selection
40-network.sh endpoints, firewall rules, and namespace lifecycle
50-runtime.sh VPN execution and app start/stop lifecycle
60-gateway.sh gateway PKI, routing, firewall, and client management
65-link-remote.sh inherit sharing, .nnslink transport, and SSH management
70-run-status.sh application execution and status reporting
90-main.sh public and internal command dispatch
tools/
check_embedded_python.py
tests/
test-static.sh
test-functions.sh
CONTRIBUTING.md contributor-facing safety and ownership invariants
CHANGELOG.md
CONTRIBUTING.md contains the implementation rules that must remain true when
changing routing, firewall, PKI, locking, configuration, or lifecycle code.
The README keeps only the user-facing architecture and operating workflow.
The generated installer remains a single executable file so it can be copied to a new Ubuntu or Fedora host without installing the source tree.
application
-> nns-my-private-app
-> OpenVPN or WireGuard
-> host NAT
-> host uplink
The application environment has its own route table and resolver configuration. The host route and host DNS remain unchanged.
application
-> nns-my-private-app
-> application-specific VPN
-> veth inside nns-my-upstream-vpn
-> upstream VPN tunnel
-> Internet
Configure a persistent upstream:
sudo nns-app install my-private-app --via my-upstream-vpnOr override the upstream for one start:
nns-app start -i my-private-app --via my-upstream-vpnBefore its own tunnel is ready, the downstream namespace can reach only its configured VPN endpoint through the upstream tunnel. If the upstream tunnel disappears, forwarding rules stop matching and traffic is dropped instead of falling back to the upstream namespace's host-facing veth.
nns-app rejects direct and indirect upstream cycles such as
my-app-a -> my-app-b -> my-app-a. Generated systemd drop-ins order downstream
namespaces after nns-online@<upstream>.service and bind their lifecycle to it.
An inherit-only child has its own namespace, resolver, process boundary, and kill switch, but does not start OpenVPN or WireGuard itself:
application
-> nns-my-shared-app
-> veth inside nns-my-remote-vpn
-> my-remote-vpn tunnel
-> Internet
Create it with:
sudo nns-app install my-shared-app \
--backend inherit \
--via my-remote-vpn
nns-app start my-shared-app
nns-app run my-shared-app firefox --no-remoteThe child OUTPUT policy permits its veth, while the upstream namespace permits
forwarding only into its verified tunnel. If the upstream stops, systemd binds
the child lifecycle to it and the forwarding/NAT path disappears; no host-uplink
fallback is installed. Inherit mode deliberately rejects --via host.
local application
-> local nns-app OpenVPN client
-> remote host OpenVPN listener
-> remote gateway TUN
-> dedicated policy table
-> transit veth
-> selected remote nns-app exit
-> remote provider tunnel
-> Internet
The remote OpenVPN listener remains in the remote host namespace. Only traffic arriving from the gateway TUN is policy-routed into the selected nns-app exit. Host-generated packets that happen to use an address from the client pool are not captured by that rule.
The gateway data path uses:
- a unique
iif <gateway-tun>policy rule; - a dedicated routing table with an explicit blackhole fallback;
- a dedicated host-to-namespace transit
/30; - dedicated tagged iptables chains;
- source-restricted forwarding;
- NAT only through the selected remote VPN tunnel;
- loose reverse-path filtering only on managed asymmetric interfaces.
The installer detects Ubuntu/Debian or Fedora and installs the matching packages
with apt-get or dnf/dnf5:
- Bash and systemd
- OpenVPN 2.6 or newer
wireguard-toolsiproute2on Ubuntu/Debian oriprouteon Fedoraiptableson Ubuntu/Debian oriptables-nfton Fedoraiputils-pingon Ubuntu/Debian oriputilson Fedoraopenssh-clienton Ubuntu/Debian oropenssh-clientson Fedora- sudo, curl, OpenSSL, Python 3, and
util-linux
On Fedora, nns-app keeps SELinux enabled and restores the standard contexts of
installed engine, systemd, and sudoers files. When firewalld is active, nns-app
creates a dedicated nns-app zone and nns-app-forward policy, assigns only its
managed host-side interfaces, and removes those global objects during purge.
The existing per-environment iptables-nft rules remain responsible for the kill
switch, endpoint restrictions, NAT, and detailed forwarding policy.
Optional transports require a locally installed binary on both ends:
stunnel4(orstunnel) for--transport stunnel;ck-serveron the gateway andck-clienton clients for--transport cloak.
The installer never downloads transport binaries from third-party release pages.
Install or refresh the engine:
sudo ./nns-app-install.sh installInstalled paths:
/usr/local/sbin/nns_app.sh
/usr/local/bin/nns-app
/etc/systemd/system/nns-netns@.service
/etc/systemd/system/nns-openvpn@.service
/etc/systemd/system/nns-online@.service
/etc/systemd/system/nns-watchdog@.service
/etc/systemd/system/nns-watchdog@.timer
/etc/systemd/system/nns-gateway@.service
/etc/systemd/system/nns-gateway-crl-refresh@.service
/etc/systemd/system/nns-gateway-crl-refresh@.timer
nns-openvpn@.service is retained as the compatibility unit name, but it
manages OpenVPN, WireGuard, a transported OpenVPN client, or an inherit-only keeper process.
Create an environment:
sudo nns-app install my-private-appImport an OpenVPN profile:
sudo nns-app add my-private-app ~/my-base-profile.ovpnOr import a WireGuard profile:
sudo nns-app add my-private-app ~/my-wireguard-profile.confStart and use the environment:
nns-app start my-private-app
nns-app status my-private-app
nns-app run my-private-app curl -4 https://api.ipify.org
nns-app run my-private-app firefox --no-remote
nns-app run my-private-app bashrun restores the invoking user’s command-search path after privilege dropping
and appends the standard /usr/local/sbin, /usr/sbin, and /sbin locations.
Interactive shells therefore retain user-installed command directories while
system diagnostics such as ip, ss, and ifconfig remain discoverable. The
rest of the environment is deliberately allow-listed rather than copied
wholesale; dangerous loader and shell-startup variables are not inherited.
Stop it:
nns-app stop my-private-appRemove the environment and its imported profiles:
sudo nns-app remove my-private-appremove refuses to delete an environment that is still configured as an
upstream for another application environment or gateway.
Strict start waits for the configured data path:
nns-app start my-private-appThe default timeout is five seconds unless changed in the application configuration. On failure, the VPN service and namespace are stopped.
Asynchronous start returns after the initial launch and leaves a slow connection retrying:
nns-app start -i my-private-appCheck it later:
nns-app status my-private-appCreate and start an upstream VPN environment:
sudo nns-app install my-upstream-vpn
sudo nns-app add my-upstream-vpn ~/my-base-profile.ovpn
nns-app start -i my-upstream-vpn
nns-app status my-upstream-vpnCreate a second environment whose VPN connection must travel through that upstream:
sudo nns-app install my-private-app --via my-upstream-vpn
sudo nns-app add my-private-app ~/my-app-profile.ovpn
nns-app start -i my-private-app
nns-app status my-private-appA one-start override does not change the saved configuration:
nns-app start -i my-private-app --via my-upstream-vpnUse --via host for a one-start direct-host override.
Download, rank, probe, and import a public OpenVPN profile:
sudo nns-app add my-private-app any
sudo nns-app add my-private-app any JP
sudo nns-app add my-private-app any GermanyForce a fresh server list:
sudo nns-app add my-private-app any US --refreshProbe candidates through an upstream environment for a local app:
sudo nns-app add my-private-app any US --via my-upstream-vpnFor an automatic-remote app, discovery and probing always run on the remote
host. --via is therefore unnecessary; only the default remote-host path (or
an explicit --via host) is accepted.
nns-app supplies VPN Gate's documented public OpenVPN credentials (vpn / vpn)
inside the root-owned managed profile and accepts a relay only after OpenVPN
reports Initialization Sequence Completed; a TLS-only peer connection is not
considered a usable profile.
VPN Gate is a volunteer network. Profiles can disappear or stop responding
without notice; use status and provider-managed profiles for reliable
deployments.
List all application environments:
nns-app listShow a detailed report:
nns-app status my-private-appShow the IP and route for the current shell context:
nns-app myipOn the host, this reports the host source/local IPv4, external IPv4, default
route, and interface. Inside a shell started with nns-app run <app> bash, the
same command automatically reports that app's tunnel path. Inspect a named app
from any host shell with:
nns-app myip my-private-appFor an automatic-remote app, the report includes the SSH remote host, managed
gateway and remote provider exit. The SSH account name is intentionally omitted
from this concise route report. myip <app> is read-only and does not start a
stopped environment.
The report distinguishes ONLINE, OFFLINE, STARTING, FAILED, and
STOPPED and includes:
- active profile and backend;
- configured and runtime upstream;
- namespace and backend service state;
- endpoint and current OpenVPN handshake stage;
- WireGuard endpoint, handshake age, and transfer totals;
- tunnel interface and address;
- external IPv4 and data-path probe;
- focused cuts from the current systemd invocation on failure.
On the remote Linux box, first create and start the nns-app environment that will provide the gateway's final Internet exit:
sudo nns-app install my-remote-exit
sudo nns-app add my-remote-exit ~/my-base-profile.ovpn
nns-app start -i my-remote-exit
nns-app status my-remote-exitCreate a gateway routed through that exit:
sudo nns-app gateway create my-relay \
--via my-remote-exit \
--listen tcp:443 \
--public vpn.example.net:443--public is written into exported client profiles. It may differ from the
local listener when a router forwards another public port.
Optional network and DNS settings:
sudo nns-app gateway create my-relay \
--via my-remote-exit \
--listen udp:443 \
--public vpn.example.net:443 \
--pool 10.200.40.0/24 \
--dns "1.1.1.1 9.9.9.9"Custom pools are rejected when they overlap:
- live host or namespace routes;
- existing application networks;
- existing gateway pools or transit networks;
- the reserved app range
10.240.0.0/16; - the reserved gateway transit range
10.239.0.0/16.
Create one client identity per local machine:
sudo nns-app gateway client add my-relay my-linux-client
sudo nns-app gateway client export my-relay my-linux-client \
--output ~/my-remote-profile.ovpnStart and inspect the remote gateway:
sudo nns-app gateway start my-relay
sudo nns-app gateway status my-relay
sudo nns-app gateway list
sudo nns-app gateway client list my-relayTransfer the exported profile over an authenticated channel. On the local box:
sudo nns-app install my-remote-vpn
sudo nns-app add my-remote-vpn ~/my-remote-profile.ovpn
nns-app start -i my-remote-vpn
nns-app status my-remote-vpn
nns-app run my-remote-vpn curl -4 https://api.ipify.orgRotate a client to a new certificate, private key, TLS Crypt v2 key, and transport identity generation:
sudo nns-app gateway client rotate my-relay my-linux-clientRevoke a lost client identity:
sudo nns-app gateway client revoke my-relay my-linux-clientRevocation and CRL generation are transactional. Active gateways are restarted
after revocation so the client session is disconnected immediately. A systemd
timer refreshes every gateway CRL weekly; gateway status shows its next
update date.
Remove the gateway and all of its private material:
sudo nns-app gateway remove my-relayThe gateway does not automatically open the host firewall or configure an external router. Permit or forward the selected TCP/UDP port separately.
Direct mode remains the default:
sudo nns-app gateway create my-relay \
--via my-remote-exit \
--listen tcp:443 \
--public vpn.example.net:443 \
--transport directFor a TLS wrapper:
sudo nns-app gateway create my-relay \
--via my-remote-exit \
--listen tcp:443 \
--public vpn.example.net:443 \
--transport stunnelFor Cloak:
sudo nns-app gateway create my-relay \
--via my-remote-exit \
--listen tcp:443 \
--public vpn.example.net:443 \
--transport cloak \
--server-name www.bing.comWrapped gateways bind OpenVPN to a private loopback port and expose only the
transport listener publicly. Cloak uses www.bing.com as its default decoy
server name; override it with --server-name, and keep it different from the
gateway's own public hostname to prevent a redirection loop. Export wrapped
gateways as .nnslink; a plain .ovpn cannot carry the wrapper configuration:
sudo nns-app gateway client export my-relay my-linux-client \
--format nnslink \
--output ~/my-remote-link.nnslink
sudo nns-app install my-remote-vpn
sudo nns-app link import my-remote-vpn ~/my-remote-link.nnslinkA bundle contains a versioned JSON manifest, the self-contained OpenVPN profile, and only the transport material required by that client. Imports reject links, devices, absolute paths, parent traversal, unknown files, oversized content, unsafe metadata, malformed Cloak settings, invalid stunnel trust material, and unsupported manifest versions. The managed client starts the transport in the application namespace before OpenVPN and stops both when either process fails.
Register a remote host once. Both machines must run nns-app 1.2.0 or newer. The first connection records its SSH host key; subsequent commands require that pinned key and non-interactive SSH/sudo:
sudo nns-app remote add edge1 --ssh user@vpn.example.netCreate a unique remote client and import its bundle locally:
sudo nns-app remote connect edge1:my-relay \
--client my-linux-client \
--name my-remote-vpnRefresh the local bundle without changing credentials, rotate the remote certificate/key generation, or inspect both management and gateway state:
sudo nns-app remote sync my-remote-vpn
sudo nns-app remote rotate my-remote-vpn
sudo nns-app remote status my-remote-vpnWhen remote sync or remote rotate replaces a bundle for a running local app,
nns-app restarts the complete namespace so endpoint routes and kill-switch rules
are rebuilt from the synchronized transport metadata.
SSH is used only for enrollment and management. The running OpenVPN/stunnel/Cloak data path connects directly to the gateway public endpoint and continues without an SSH session.
Each managed gateway has:
- a private CA;
- a unique certificate and private key per client;
- a unique TLS Crypt v2 client key;
- TLS Crypt v2
force-cookie; - TLS 1.2 minimum;
- AEAD data ciphers;
- a certificate revocation list;
- atomic configuration and CRL replacement.
Administrative gateway and PKI operations are serialized with flock.
Gateway creation and client enrollment use staging directories and publish
their final state only after validation.
Direct OpenVPN with TLS Crypt v2 is harder to fingerprint than a plain OpenVPN profile, but it is not indistinguishable from HTTPS. Strong protocol filtering may still require a separate camouflage transport.
Each application environment has:
/etc/nns-app/<name>/<name>.cfg
Important fields:
DEFAULT_PROFILE=""
VPN_TYPE=""
KILLSWITCH="on"
AUTOSTART="off"
UPSTREAM_APP=""
WAN_IFACE="auto"
DNS_SERVERS="1.1.1.1 9.9.9.9"
DISABLE_IPV6="on"
DISABLE_DCO="off"
PROFILE_FIXUPS="on"
READY_TIMEOUT="5"
EXTERNAL_IP_URL="https://api.ipify.org"
WATCHDOG_MODE="auto"
WATCHDOG_FAILURES="3"
WATCHDOG_COOLDOWN="300"
TRANSPORT_TYPE="direct"
TRANSPORT_REMOTE_HOST=""
TRANSPORT_REMOTE_PORT=""
TRANSPORT_LOCAL_PORT=""
TRANSPORT_CONFIG=""
TRANSPORT_SSH_TARGET=""
TRANSPORT_SSH_IDENTITY=""
TRANSPORT_SSH_KNOWN_HOSTS=""
TRANSPORT_SSH_REMOTE_PORT=""
REMOTE_MODE=""
REMOTE_ALIAS=""
REMOTE_GATEWAY=""
REMOTE_CLIENT=""
REMOTE_OWNER_ID=""
REMOTE_EXIT_APP=""
REMOTE_PROFILE_GENERATION=""
REMOTE_SERVER_FINGERPRINT=""The ordinary local/manual install default is AUTOSTART="off". Simple
automatic-remote mode changes it to on after install ... via --remote and
starts/enables the environment after profile deployment.
Configuration files are root-owned and rejected when group/world writable.
WATCHDOG_MODE="auto" starts a lightweight systemd timer only while an
OpenVPN or WireGuard environment is running. It does not monitor inherit-only
children because their recovery belongs to the upstream environment.
Every 30 seconds the watchdog checks the real namespace data path. It arms only
after that environment has been online at least once, so it does not interfere
with a slow initial VPN connection. A single failed probe does nothing. After
WATCHDOG_FAILURES consecutive failures it restarts only
nns-openvpn@<app>.service; the network namespace and processes already
running inside it remain alive. WATCHDOG_COOLDOWN prevents restart loops.
When an app is chained through another nns-app environment, recovery is
deferred while that upstream is offline.
Use WATCHDOG_MODE="off" for profiles that must never be restarted
automatically. nns-app status <app> reports the timer, failure count, last
result, and watchdog-triggered restart attempts.
All known fields are reset before every load, preventing values from a previously loaded application or gateway from leaking into another object.
After manually changing UPSTREAM_APP, refresh generated systemd dependencies:
sudo nns-app install <name>OpenVPN imports reject root-level directives that can execute scripts, load
plugins, include arbitrary files, or expose management interfaces. Managed
copies can add compatibility directives when PROFILE_FIXUPS="on".
WireGuard imports accept full-tunnel IPv4 client profiles. These directives are rejected:
PreUp
PostUp
PreDown
PostDown
SaveConfig
Namespace DNS remains authoritative instead of being delegated to wg-quick.
ip netns exec creates a private mount namespace for namespace-specific
resolver files. Snap launchers also require cgroup v2 and securityfs there.
nns-app run detects direct Snap aliases, the Snap launcher itself, and distro
transition wrappers such as a distribution-provided /usr/bin/firefox wrapper. It mounts those
filesystems inside the private command mount namespace before dropping to the
configured desktop user. Ordinary commands and non-Snap desktop applications
skip this extra mount preparation.
nns-app run my-private-app firefox --no-remoteThe temporary mounts disappear with the command and do not alter the host mount table.
Install the current pre-built file:
sudo ./nns-app-install.sh installThe installer refreshes systemd templates and generated dependency drop-ins. On Fedora it also installs the distribution-specific dependency names and reconciles the dedicated firewalld zone and forwarding policy when firewalld is running.
A gateway still running from 1.0.23 must be restarted once so the current policy-rule and dedicated firewall-chain model replaces the legacy rules:
sudo nns-app gateway stop my-relay
sudo nns-app gateway start my-relayThe installer prints a warning when it detects a running gateway that requires that migration restart.
Application environment:
nns-app status my-private-app
sudo journalctl \
-u nns-netns@my-private-app.service \
-u nns-openvpn@my-private-app.service \
-n 150 -o cat --no-pagerManaged gateway:
sudo nns-app gateway status my-relay
sudo journalctl \
-u nns-gateway@my-relay.service \
-n 150 -o cat --no-pagerInspect namespace and WireGuard state:
sudo ip -n nns-my-private-app address
sudo ip -n nns-my-private-app route show table all
sudo ip netns exec nns-my-private-app wg showInspect a chained or gateway data path:
sudo ip rule show
sudo ip route show table all
sudo iptables-save
sudo ip netns exec nns-my-remote-exit iptables-save- IPv4 client data paths only.
- Ubuntu and Fedora with systemd are supported; other distributions require manual dependency and firewall validation.
- Managed gateways use an OpenVPN server backend only.
- Router port forwarding and host INPUT firewall changes are not automated.
- Client and server certificate renewal requires issuing a new client identity or recreating the gateway before certificate expiry.
- Direct OpenVPN gateway transport is not a full DPI-camouflage protocol.
Read CONTRIBUTING.md before changing networking, firewall, route ownership,
PKI, locking, or systemd lifecycle code. Run:
make testbefore committing source or generated-installer changes.
GPL-3.0-or-later. See LICENSE.