Start your 3-day free trial
Sign up to experience all premium features at no cost.
*Available only to new users. Each user is limited to one trial.


Natter port mapping attempts to maintain a NAT mapping and forwards to a local service; a STUN address alone cannot prove authenticated external reachability. This untested lab uses Ubuntu 24.04 LTS x64, Python 3.12, Natter 2.2.1, and one TCP endpoint.
Key Takeaways
- Use only authorized networks, services, and external clients.
- Serve a fixed harmless response through mutual TLS, never a management interface.
- Retain one-source firewall access and certificate verification.
- Stop services and revoke access; mappings can outlast keepalive.
STUN reports server-observed addresses. Mapping and filtering are separate and can vary by destination/network; RFC 8489 does not guarantee universal inbound access or CGNAT compatibility.[3]
Define access and cleanup with the ownership checklist. Use the CGNAT explainer and home NAT troubleshooting to interpret prerequisites, not certify reachability.
For outbound privacy, compare VPN exits and inbound forwarding; hosting adds different responsibilities. This lab offers no bypass guarantee, ISP success rate, or third-party scanning.
The VPN routing introduction covers outbound access, not this endpoint's reachability.
Use an authorized dedicated Ubuntu host and separate-network external client. Reserve LAN 192.168.50.10, Natter TCP 58961, and loopback TLS 8443; replace the LAN address throughout. Record routes, IPv4/IPv6 addresses, listeners, firewall rules, and the external public source.
Require tested independent management and an already active operator-owned UFW policy denying unsolicited inbound traffic, without competing firewall managers. Otherwise prepare a dedicated host. Never blindly enable UFW over SSH, flush/disable protection, or modify production NGINX.
Natter NAT requirements include permitted TCP STUN/keepalive and compatible mapping/filtering. Multiple NAT layers or destination-dependent behavior can block access. A private WAN address is only a topology clue. Never compensate with DMZ, UPnP, or exposed NAS/RDP/databases.
Stop if NGINX, its configuration, service, or mask already exists. On a fresh host, mask before trusted-repository installation to prevent Ubuntu's default-site startup; record added packages/versions.[8][9]
sudo systemctl mask nginx.service
sudo apt update
sudo apt install nginx openssl curl python3
systemctl is-enabled nginx.service
systemctl is-active nginx.service
ss -ltnp 'sport = :80 or sport = :8443 or sport = :58961'
readlink /etc/nginx/sites-enabled/default
Require masked/inactive (nonzero status exits) and unoccupied listed ports. Installation failure or unexpected startup requires cleanup. Only if readlink identifies this installation's /etc/nginx/sites-available/default, run sudo unlink /etc/nginx/sites-enabled/default; otherwise stop. Preserve other sites.
Confirm loopback with ip route get 127.0.0.2. Natter 2.2.1 rewrites exact target 127.0.0.1 to LAN; keep listener and target at 127.0.0.2.[7] Create private two-day credentials:[5][6]
mkdir -m 700 "$HOME/natter-lab"
cd "$HOME/natter-lab"
umask 077
openssl req -x509 -newkey rsa:3072 -nodes -days 2 -keyout ca.key -out ca.crt -subj '/CN=Natter Lab CA'
openssl req -new -newkey rsa:3072 -nodes -keyout server.key -out server.csr -subj '/CN=natter-lab.example'
printf 'subjectAltName=DNS:natter-lab.example\nextendedKeyUsage=serverAuth\n' > server.ext
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 2 -extfile server.ext -out server.crt
openssl req -new -newkey rsa:3072 -nodes -keyout client.key -out client.csr -subj '/CN=Owned external client'
printf 'extendedKeyUsage=clientAuth\n' > client.ext
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAserial ca.srl -days 2 -extfile client.ext -out client.crt
sudo install -d -m 700 /etc/nginx/natter-lab
sudo install -m 600 server.key /etc/nginx/natter-lab/server.key
sudo install -m 644 server.crt ca.crt /etc/nginx/natter-lab/
Keep ca.key private on the operator host. Transfer only ca.crt, client.crt, and client.key through authenticated transport to the authorized client; restrict key permissions. Never publish private keys or store them on shared drives.
Use sudoedit to save /etc/nginx/conf.d/natter-lab.conf: loopback-only, fixed /check response, lab-signed client certificate required. Never reuse a CA trusting other users.[4]
server {
listen 127.0.0.2:8443 ssl;
server_name natter-lab.example;
ssl_certificate /etc/nginx/natter-lab/server.crt;
ssl_certificate_key /etc/nginx/natter-lab/server.key;
ssl_client_certificate /etc/nginx/natter-lab/ca.crt;
ssl_verify_client on;
ssl_protocols TLSv1.2 TLSv1.3;
access_log off;
location = /check { return 200 "natter-lab-ok\n"; }
location / { return 404; }
}
Inspect all nginx -T output before unmasking: only 127.0.0.2:8443 ssl, no default/IPv6 listener; otherwise stop. Validate, unmask, disable boot startup, start, and inspect ss. The first local request must return the fixed response; the certificate-free request must not.
sudo nginx -T
sudo nginx -t
sudo systemctl unmask nginx.service
sudo systemctl disable nginx.service
sudo systemctl start nginx.service
ss -ltnp 'sport = :80 or sport = :8443 or sport = :58961'
curl --noproxy '*' --cacert ca.crt --cert client.crt --key client.key --resolve natter-lab.example:8443:127.0.0.2 https://natter-lab.example:8443/check
curl --noproxy '*' --cacert ca.crt --resolve natter-lab.example:8443:127.0.0.2 https://natter-lab.example:8443/check
On failure, clean created resources through Step 4; remove any remaining lab mask with sudo systemctl unmask nginx.service. Retain expiry/name verification; never use curl -k.
Inspect the official 2.2.1 script in the private lab directory. Run under your normal account; elevate only configuration/firewall administration.[1][2]
-i selects the address used for STUN/keepalive, while socket forwarding binds all IPv4 addresses. Inspect sudo ufw status verbose and sudo ufw show raw: require every-interface default-deny and no broader 58961 allowance; otherwise stop. Replace OWNED_CLIENT_PUBLIC_IP with the owned external source visible here, allowing only it to the reserved LAN address.[7]
curl --fail --location --proto '=https' --proto-redir '=https' -o natter.py https://github.com/MikeWang000000/Natter/releases/download/2.2.1/natter.py
python3 natter.py --version
sudo ufw allow in proto tcp from OWNED_CLIENT_PUBLIC_IP to 192.168.50.10 port 58961 comment 'natter-lab'
python3 natter.py -i 192.168.50.10 -b 58961 -m socket -t 127.0.0.2 -p 8443 -q
In another terminal, inspect ss -ltnp 'sport = :8443 or sport = :58961': NGINX must use 127.0.0.2:8443, Natter 0.0.0.0:58961. Firewall restrictions must limit source/destination. Add no IPv6 or router forwarding.
-m socket forwards to loopback TLS without NAT-table rules; -q exits on mapping changes. Add no -U, daemon, retry loop, notification script, or startup service. Use configured STUN/keepalive endpoints only where policy permits; otherwise stop.
Privately record public IP/port; the port may differ from 58961. The built-in tester may be blocked by the one-source rule. Judge reachability using the next owned-client request; never widen access for that tester.
Use the separate-network client with its still-authorized public source and private credentials. Same-LAN hairpin requests cannot establish external reachability.
Replace PUBLIC_IP and both PUBLIC_PORT occurrences with the current mapping. --resolve preserves TLS identity natter-lab.example without DNS/hosts edits. Per-request --noproxy '*' leaves existing proxy/VPN configuration unchanged.
curl --noproxy '*' --connect-timeout 10 --max-time 20 --cacert ca.crt --cert client.crt --key client.key --resolve natter-lab.example:PUBLIC_PORT:PUBLIC_IP https://natter-lab.example:PUBLIC_PORT/check
curl --noproxy '*' --connect-timeout 10 --max-time 20 --cacert ca.crt --resolve natter-lab.example:PUBLIC_PORT:PUBLIC_IP https://natter-lab.example:PUBLIC_PORT/check
Require fixed text with authentication and rejection without a client certificate. Inspect HTTP status/body: curl can exit successfully on HTTP rejection. Wrong CA or uncertified server name must fail identity verification; never suppress TLS checks.
| Reader observation | Supported conclusion | Next action |
|---|---|---|
| STUN cannot report a usable address | This STUN path is unavailable | Check permitted egress and stop if unresolved |
| Mapping appears, external TCP times out | Reachability is not established | Inspect source rule, mapping/filtering, and NAT layers |
| TCP connects but TLS identity fails | The expected service identity is unverified | Check SAN, CA, endpoint, and expiry; keep validation enabled |
| Authenticated request works, missing certificate also works | Access control is incorrect | Stop Natter and repair client-certificate enforcement |
| Mapped address changes and Natter exits | The recorded endpoint is stale | Recheck the network and restart manually only after review |
Failed service requests remain failures. Browser STUN/WebRTC address exposure and authenticated inbound access are distinct. Neither proves the other.
This listener is IPv4-only. Audit other services' IPv6 exposure separately; preserve existing protections and investigate only owned resources. This lab does not secure the entire host.
Press Ctrl+C in Natter's foreground terminal. Delete only its source rule, stop/disable the new NGINX, and remove lab configuration/credentials. Require no listed listener:
sudo ufw delete allow in proto tcp from OWNED_CLIENT_PUBLIC_IP to 192.168.50.10 port 58961
sudo systemctl disable --now nginx.service
sudo rm /etc/nginx/conf.d/natter-lab.conf
sudo rm /etc/nginx/natter-lab/server.key /etc/nginx/natter-lab/server.crt /etc/nginx/natter-lab/ca.crt
sudo rmdir /etc/nginx/natter-lab
ss -ltnp 'sport = :80 or sport = :8443 or sport = :58961'
Mappings may linger after keepalive stops. After removing listeners/services, repeat the authenticated external request to the last mapping: no lab response may return. Timeout/refusal proves endpoint unavailability, not instant deletion of router state.
Delete temporary credentials on both hosts, restore only lab changes, and verify management. Leave the default site disabled. Remove only recorded new packages without other users; avoid broad autoremove. If setup remained masked, stop the new service before removing its lab mask. Lab CA/service removal leaves unrelated certificates unaffected.
Before updating, retain the prior script, sanitized invocation, private configuration backup, and baseline. Repeat positive, rejection, changed-address, and cleanup checks; never add infinite unattended retries.
STUN reports an observed address; mapping and filtering can still block your external client's service request.
No. Compatible mapping/filtering, permitted traffic, and a successful external service check are still required.
Harmless content limits exposed data; client certificates restrict recipients of an internet-reachable response.
Local checks verify service/authentication prerequisites. Only a separate-network client establishes external reachability.
The one-source rule can reject automatic testers while allowing your owned client's authenticated request.
Router state may linger. Stop services, revoke rules/credentials, and repeat the external request.
No. Use this short-lived fixed-response TLS endpoint; management interfaces require different permissions and risk analysis.
Disclaimer: Use authorized infrastructure and permitted networks only. These are expected checks, without completed author-network measurements or ISP success-rate claims.
Sources:
Sources checked 11 October 2026.
Related Articles:
Sign up to experience all premium features at no cost.
*Available only to new users. Each user is limited to one trial.