EST. 2026 — Notes from real server work SYSADMIN · SECURITY · AI · DIRECTADMIN Issue 01
THE SYSADMIN ALMANAC
Home / Network / Home Internet Behind CGNAT: Checking an ISP's 10-Port Limit
Network

Home Internet Behind CGNAT: Checking an ISP's 10-Port Limit

by Ton-Or ·31 Oct 2026·11 min read
Home Internet Behind CGNAT: Checking an ISP's 10-Port Limit

Every port forwarding rule can be correct and outside connections can still fail, because some ports never get past the ISP's edge in the first place, and the testing tools I usually use can give misleading results too.

1-minute summary

Symptom / problem
Port forwarding is set up correctly but inbound still fails, and the port test button is always red.
Who should read
Home users behind CGNAT whose ISP gives DDNS and only a limited port window.
Fix in short
Keep external ports inside the 10-port window the ISP opens, and test from a real outside machine while watching the destination instead of trusting the port test button.
Jump to steps

I had finished setting up every port forwarding rule on my home router, but connections from outside still failed. The problem was not the router, even though that is always where I check first. My home uses fiber from a Thai ISP behind CGNAT, with no public IP of its own. Even with ports open on the router, outside clients cannot reach it. The ISP therefore provides a DDNS service to keep a domain name pointing to my home, and forwards incoming traffic only on an allocated set of 10 ports. This article covers those rules, a configuration I discovered had never worked without anyone noticing, how to test without getting sidetracked, and what I still have not resolved. All domain names and port numbers in this article are examples.

Symptoms: Everything is configured, but outside connections fail

The problem looked like this: the router had all the forwarding rules, the destination machine at home was listening on the port, and connections within the home network worked normally. But when I used a phone with Wi-Fi turned off to connect to the DDNS name and port, nothing responded. I worked through the checklist below in order, but first I needed to understand the service's rules.

10 portsRange allocated by the ISP
40000-40009Example range
1 ruleOutside the range, never worked
3 methodsTests I encountered

The ISP allows only 10 ports: external must be in range, internal can be anything

The service manual gives three configuration requirements: the external or WAN port on the router must match an allocated port; the internal port is the service's actual port and can be anything, whether 80, 443, 8006 or 22; and the internal IP is the receiving machine at home. From outside, connect to home.example.net:<พอร์ตในช่วง>. A common misunderstanding is that the internal and external port numbers must match. For example, if the machine listens on 8006, people assume they must also open 8006 externally. That is unnecessary, and if 8006 is outside the allocated range, it will never be reachable.

A street lined with mailboxes, with only ten numbered boxes available for use, other people's mail piled outside, and a confused delivery worker holding a parcel numbered 2200
The ISP forwards only to the 10 allocated mailboxes. Parcels addressed to numbers outside that range are left at the ISP's front door and never reach me.
Out-of-range ports are dropped at the ISP edgeInternet(1) home.example.net:40003:2200 dropped hereISP edgeAllocated range40000-40009(2) CGNAT forwardsHome router(3) 40003 to 8006on host-ahost-a:8006Home settings cannot help if external is outside the ISP's allocated range
Steps 1 to 3 show the correct path. The red arrow is an out-of-range port that never gets past the ISP's edge, no matter how many rules are configured on the home router.

An example table of valid rules (all values are examples)

ชื่อกฎ        external   internal   โปรโตคอล   internal IP
web-admin     40003      8006       TCP        192.168.10.50
ssh-host-a    40005      2200       TCP        192.168.10.50

The old SSH rule that had never worked from outside

While checking the old rules, I found an SSH forwarding rule with external port 2200, which was outside the range. That meant it had never worked from outside even once, and nobody knew because nobody had actually tested it from outside. Connections inside the home network worked normally, so everything had always seemed fine.

I fixed it by moving the external port to an unused port within the range (keeping the internal port at 2200), which matched the rules above exactly. During the cutover, I temporarily configured DMZ on the ISP router to point to the new gateway's WAN, creating double NAT, then configured the actual forwarding rules on the gateway. I have already covered the router cutover sequence in my article on switching home internet to bridge mode, so I will not repeat it here.

A rule never tested from outside might as well not exist

A configuration that looks correct in the router interface proves nothing about whether it actually works from the internet. My old SSH rule had always passed a visual check in the router interface. The problem was a single number, and the router interface had no way to tell me.

A five-point checklist before blaming anyone

When forwarding fails, I go through these one at a time, without skipping any.

  1. Is the external port within the range allocated by the ISP? Open the DDNS service page and compare the numbers against each rule.
  2. Does the forwarding rule point to the correct machine's internal IP and internal port, and does it use TCP or UDP as required by the service?
  3. Is the destination machine actually listening on that port? Check with `ss -ltnp`.
  4. Check that the destination machine's firewall and the gateway's ACL are not blocking it.
  5. Test from genuinely outside using a third machine, then check whether a connection appears on the destination machine.

The first three checks can all be done from inside the home network.

# บนเครื่องปลายทาง (host-a): ฟังพอร์ตนี้อยู่จริงไหม
sudo ss -ltnp | grep ':8006'

# firewall: ดูกฎ INPUT ว่ามีอะไรบล็อกพอร์ตนี้หรือเปล่า
sudo iptables -S INPUT | head -3

In my case, every check passed: the peer-port matched, forwarding was correct for both TCP and UDP, and nothing was blocking traffic along the way. So why did it still look like a failure? The answer was in point five and the testing tool I had picked.

The trap: the torrent port test button always shows red

I used a torrent client (Transmission) for testing because it has a button to check whether the port is open. It kept reporting "port is Closed" even after I had verified every point above. The reason is that the button asks an external website to connect back to my outbound IP, which under CGNAT is shared with other customers. The result therefore always shows red, whether my forwarding is correct or not.

What made it more confusing was that peers could still connect, as shown in the destination machine's conntrack, with external UDP traffic reaching the torrent port. But those peers could connect because I had initiated connections to them, and because uTP could traverse NAT through outbound traffic. In other words, "seeing peers" does not mean "inbound works," and "a red port test" does not mean "forwarding is wrong." Neither observation proves anything.

One large water pipe branching into ten small taps, with a pressure gauge labeled port test closed attached to the wrong pipe
The port test button measures the wrong pipe. It measures the main pipe shared by many homes, not the small tap the ISP allocated to me.
Three inbound tests: only C is reliableA port testTorrent client buttonCalls back to outbound IPshared with other usersAlways ClosedProves nothingB Peers appearPeers I connected toUDP or uTP traverses NATAppears in conntrackDoes not proveinbound worksC External testhost-b holds TCP openDDNS name, in-range portThen check destinationActual evidencess -tn or conntrack
Rows A and B produce results that look like answers but are not. Only row C involves someone outside actually connecting in and that connection appearing at the destination.

The right test: connect from outside, then check the destination

The method I could confirm was valid was to have an external server (host-b) hold a TCP connection open to the DDNS name on a port within the range, then check whether a connection from host-b appeared on the destination machine at home.

# บน host-b (เซิร์ฟเวอร์ภายนอก): เปิด TCP ค้างไว้ 2 นาที
sleep 120 | nc -v home.example.net 40003

# บน host-a (ปลายทางในบ้าน) ระหว่างที่ host-b ค้างอยู่:
sudo ss -tn state established '( sport = :8006 )'
sudo conntrack -L 2>/dev/null | grep 203.0.113.50

Testing from inside the home network can produce misleading results because of NAT loopback. Use a phone on 4G or 5G with Wi-Fi turned off instead. There are two outcomes to distinguish: if host-b connects and the destination sees the connection, inbound really works; but if host-b connects and the destination sees nothing, the device accepting the connection is at the ISP's edge, not my machine.

Connection succeeds

That does not mean it reached my machine

A device along the way may accept the TCP handshake instead, so I need to check that the connection actually appears at the destination. The result from the connecting side is not enough.

Destination sees the connection

This is evidence

If ss or conntrack on the destination machine shows host-b's IP, the packets have passed through the ISP's edge and my router to reach the actual machine.

Still unresolved: after bridge mode, traffic does not reach the gateway

I need to be clear here that this remains unresolved. After the ISP switched its device to bridge mode and my gateway took over PPPoE, testing showed that an outside client could establish TCP connections to every port on the ISP's edge IP (it accepted handshakes on every port), but traffic did not reach my gateway. This is why method C requires checking at the destination. If I had trusted "it connected" alone, I would have thought the port was open.

I still cannot say why. The only thing I have actual evidence for is that no packets reach my side; I do not yet know which side is responsible. These are the things I have not tried yet and have written down as a plan.

Things I have not tried yet

Confirm the ports again on the DDNS service page; call the ISP to ask whether this service works with PPPoE on my own router; request a public IP; use IPv6 (the ISP provides IPv6, but I have not enabled it on my gateway); or work around it with a tunnel and VPN, which I have already covered in [my article on WireGuard from a VPS to home](/article/wireguard-vps-home-vpn), [my article on a stale WireGuard DDNS endpoint](/article/wireguard-ddns-endpoint-stale-reresolve), and [my article on a cloudflared tunnel](/article/cloudflared-tunnel-single-ingress-remote-desktop).

Things I cannot yet answer and will not guess about include whether the ISP's DDNS service can bind to a PPPoE session, whether IPv6 inbound actually works, and whether the problem is definitely on the ISP's side. I have no evidence for any of those three points yet. Another side of the same problem is that when the IP moves behind CGNAT, an allowlist tied to the old IP breaks. I covered that in my article on SSH from restrictions and CGNAT.

Summary

If I am behind CGNAT with ISP-provided DDNS and a 10-port range, the external port must be within that range; the internal port can be anything. I check every old rule to make sure its external port is in range, test from a third machine genuinely outside the network, then check ss and conntrack at the destination. I do not trust the torrent port test button or assume that "seeing peers" means inbound works. As for this case after switching to bridge mode, I still have no conclusion. Anyone with similar symptoms will have to decide whether to wait for an answer from the ISP or move straight to a tunnel.

If I have misunderstood any of the DDNS service's rules, or if I find the reason traffic does not reach the gateway after switching to bridge mode, I will come back and update this article.

Want us to set this up?

Get help with server setup, configuration, migration, or troubleshooting. Save time for the work that matters.

Get a quote

Hit something like this? Stuck somewhere?

Come trade notes on the LinuxThai board

→ Continue on the board

Comments 0

No comments yet — be the first