Guide

NAT Not Working on a Cisco Router: Inside, Outside and ACL Checks

Broken NAT rarely gives a clear error. Inside hosts cannot reach anything outside, the router itself can, and the configuration looks right at a glance. This guide covers diagnosis, not setup. To build NAT from scratch, start with NAT overload (PAT) or static NAT. The checks below run in the order that finds faults fastest: send test traffic from the right place, read the interface roles, prove the access list matches, then follow the reply home. Every block of output here was captured on Cisco IOL routers running IOS XE 17.16, each fault first with the break in place and then after the fix.

Private inside, one public address outside — the reference wiring for NAT & PAT, not a specific lab.

The test network

Three IOS XE routers stand in for a small site. HOST is the inside client. Its primary address is 192.168.10.10, and two secondaries, 192.168.10.11 and 192.168.10.20, let one node act as three inside hosts. EDGE is the NAT router: Ethernet0/0 (192.168.10.1/24) faces the LAN, and Ethernet0/1 (203.0.113.1/29) faces the provider. ISP hosts the server address 198.51.100.10 on a loopback.

EDGE overloads the whole LAN onto its outside interface address and maps 192.168.10.20 statically to 203.0.113.5. Every fault below is a change to this working configuration.

One detail makes this a fair test bench: ISP has no route to 192.168.10.0/24, just as a real provider does not route private space, and a lookup there answers % Network not in table. An untranslated packet from HOST still reaches the server, but the reply has nowhere to go. A ping that succeeds from HOST therefore proves that translation happened.

EDGE(config)# interface Ethernet0/0
EDGE(config-if)# ip address 192.168.10.1 255.255.255.0
EDGE(config-if)# ip nat inside
EDGE(config-if)# exit
EDGE(config)# interface Ethernet0/1
EDGE(config-if)# ip address 203.0.113.1 255.255.255.248
EDGE(config-if)# ip nat outside
EDGE(config-if)# exit
EDGE(config)# ip route 0.0.0.0 0.0.0.0 203.0.113.2
EDGE(config)# access-list 1 permit 192.168.10.0 0.0.0.255
EDGE(config)# ip nat inside source list 1 interface Ethernet0/1 overload
EDGE(config)# ip nat inside source static 192.168.10.20 203.0.113.5

ISP# show ip route 192.168.10.0
% Network not in table

The working NAT configuration on EDGE, and proof that ISP cannot route replies to the private range. Output captured on Cisco IOL (IOS XE 17.16) in Cisco Modeling Labs.

Step 1 — Test from an inside host, not from the router

Most people ping from the NAT router first, and that test tells you nothing about NAT. EDGE sends its own ping from its outside address, 203.0.113.1. The packet never arrives on an ip nat inside interface, so it is never a candidate for translation, and it succeeds whether NAT works or not.

Both pings below ran while NAT was broken (Step 2 shows the fault). EDGE reaches the server at 100 percent. HOST, behind it, gets nothing back — five dots, not five unreachables, because the request left untranslated and the reply was simply stranded.

Test from a real inside host, or from a LAN router with ping <destination> source <inside address>, as HOST does here. Run clear ip nat translation * on the NAT router first, so every entry you read afterward came from your test. The command removes only dynamic entries; static mappings stay in the table. ICMP entries are short-lived, a minute by default, so read the table right after the ping.

EDGE# ping 198.51.100.10
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 198.51.100.10, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/1 ms

HOST# ping 198.51.100.10
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 198.51.100.10, timeout is 2 seconds:
.....
Success rate is 0 percent (0/5)

Same moment, same broken NAT: the router's own ping succeeds and the inside host's fails. Output captured on Cisco IOL (IOS XE 17.16) in Cisco Modeling Labs.

Step 2 — Check the inside and outside roles

NAT only acts on packets that cross from an ip nat inside interface to an ip nat outside interface, or back. An inside source rule rewrites the source address on the way out and reverses the change on the way back. If either role is missing, or the two are swapped, the rules stay in the configuration but never fire.

show ip nat statistics lists the roles near the top, which is faster than reading every interface. Here the outside role was removed from Ethernet0/1. The heading Outside interfaces: prints with nothing under it, and further down the dynamic mapping reads refcount 0, the count of translations currently using that rule. The translation table holds only the static entry, which the configuration creates whether or not traffic flows. The Hits counter is cumulative, so one reading of it settles nothing; the empty list does.

Compare the two lists with the real traffic path. The interface where host traffic arrives must be inside, and the interface your route to the destination uses must be outside. show ip route tells you which interface that is. On a router with more than one WAN link, every link that can carry translated traffic needs the outside role.

EDGE(config)# interface Ethernet0/1
EDGE(config-if)# no ip nat outside
EDGE(config-if)# end

EDGE# show ip nat statistics
Total active translations: 1 (1 static, 0 dynamic; 0 extended)
Outside interfaces:
Inside interfaces: 
  Ethernet0/0
Hits: 49  Misses: 0
CEF Translated packets: 49, CEF Punted packets: 5
...
Dynamic mappings:
-- Inside Source
[Id: 1] access-list 1 interface Ethernet0/1 refcount 0
...

EDGE# show ip nat translations
Pro Inside global      Inside local       Outside local      Outside global
--- 203.0.113.5        192.168.10.20      ---                ---

With the outside role gone, NAT lists no outside interface and holds only the static entry. Output captured on Cisco IOL (IOS XE 17.16) in Cisco Modeling Labs.

Restore the role and read a healthy table

Put the role back, clear the table and test again from two inside addresses. Each PAT row now pairs a private inside local address with EDGE's outside address, 203.0.113.1, as the inside global. Both outside columns carry the server's address, 198.51.100.10, unchanged, because inside source NAT rewrites only the inside side of the flow. If inside local and inside global are new to you, what NAT is explains them.

For ICMP, the number after the colon is the echo identifier, which stands in for a port. On the PAT rows the inside local column keeps the identifier the host chose, 7 and 8 here, while the inside global side carries a replacement that IOS XE allocated from 1024 upward. The router keeps that pairing for the life of the entry, so each reply reaches the host that sent the request.

EDGE(config)# interface Ethernet0/1
EDGE(config-if)# ip nat outside
EDGE(config-if)# end
EDGE# clear ip nat translation *

HOST# ping 198.51.100.10
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 198.51.100.10, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/1 ms
HOST# ping 198.51.100.10 source 192.168.10.11
! same result from the second inside address

EDGE# show ip nat translations
Pro Inside global      Inside local       Outside local      Outside global
icmp 203.0.113.1:1024  192.168.10.10:7    198.51.100.10:7    198.51.100.10:1024
icmp 203.0.113.1:1025  192.168.10.11:8    198.51.100.10:8    198.51.100.10:1025
--- 203.0.113.5        192.168.10.20      ---                ---

Two inside hosts, one outside address, and the static row still there. Output captured on Cisco IOL (IOS XE 17.16) in Cisco Modeling Labs.

Step 3 — Prove the access list matches the real source

The list in ip nat inside source list does not filter anything. It selects which inside sources get translated. A source the list does not permit is still routed, just with its private address intact, so it reaches the far side and loses the reply.

The classic mistake is typing a subnet mask where IOS expects a wildcard mask. IOS accepts access-list 1 permit 192.168.10.0 255.255.255.0 without a warning, masks the address against the wildcard it was handed, and stores the result. show access-lists then reads permit 0.0.0.0, wildcard bits 255.255.255.0: your subnet has disappeared from the entry, and what is left matches only addresses whose last octet is zero. Note that IOS prints no match count at all while the counter is zero, so an entry with no (n matches) suffix is an entry nothing has hit.

The static mapping makes a useful control. Static NAT does not consult the list, so 192.168.10.20 keeps working while every PAT user fails, and the only flow in the translation table belongs to that host — with the same ICMP identifier on both sides, because nothing had to be swapped for it. When one host works and its neighbors do not, compare the list with the addresses that fail, and do not treat a statically mapped host as proof that the list is right.

A rule that names a list which does not exist behaves the same way, so check that the number or name in the NAT rule matches the list you edited. So does a deny entry that matches your hosts above the permit. The wildcard rules are the same as for any standard ACL.

EDGE(config)# no access-list 1
EDGE(config)# access-list 1 permit 192.168.10.0 255.255.255.0
EDGE(config)# end

EDGE# show access-lists 1
Standard IP access list 1
    10 permit 0.0.0.0, wildcard bits 255.255.255.0

HOST# ping 198.51.100.10
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 198.51.100.10, timeout is 2 seconds:
.....
Success rate is 0 percent (0/5)

HOST# ping 198.51.100.10 source 192.168.10.20
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 198.51.100.10, timeout is 2 seconds:
Packet sent with a source address of 192.168.10.20 
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/1 ms

EDGE# show ip nat translations
Pro Inside global      Inside local       Outside local      Outside global
icmp 203.0.113.5:8     192.168.10.20:8    198.51.100.10:8    198.51.100.10:8
--- 203.0.113.5        192.168.10.20      ---                ---

A subnet mask typed as a wildcard: PAT users fail, and the statically mapped host still works. Output captured on Cisco IOL (IOS XE 17.16) in Cisco Modeling Labs.

Fix the list and read its counter

Replace the entry with the correct wildcard, clear the table and send one ping. The match counter on a NAT list counts translations created, not packets, so a single five-packet ping adds one match. A counter that climbs slowly is normal. A counter that never moves while the failing hosts send traffic means the list does not match them.

IOS XE stores a numbered list in the running configuration as ip access-list standard 1 with sequence numbers, so read the list with show access-lists rather than searching the configuration for the line you typed.

EDGE(config)# no access-list 1
EDGE(config)# access-list 1 permit 192.168.10.0 0.0.0.255
EDGE(config)# end
EDGE# clear ip nat translation *

HOST# ping 198.51.100.10
! one five-packet ping from 192.168.10.10

EDGE# show access-lists 1
Standard IP access list 1
    10 permit 192.168.10.0, wildcard bits 0.0.0.255 (1 match)

The corrected entry, and one match for the one translation it built. Output captured on Cisco IOL (IOS XE 17.16) in Cisco Modeling Labs.

Step 4 — Translations appear but replies never return

Sometimes the table fills with entries for your test, yet the host still gets no reply. Translation worked, but the return path did not. The server addresses its reply to the inside global address, and something between the server and the NAT router cannot deliver it.

To show this, 192.168.10.11 was given a static mapping to 203.0.113.17, which is outside EDGE's 203.0.113.0/29 subnet. EDGE builds the translation and forwards the request, so the table looks healthy while the ping reports 0 percent. ISP answers that address with % Subnet not in table — rather than the % Network not in table it gave for the private range, because it already has a connected route inside 203.0.113.0/24 — and both answers mean the same thing: no route, so the reply never leaves.

EDGE answers ARP on the outside segment for its own interface address and for an inside global address that falls inside that connected subnet, which is why the static map to 203.0.113.5 works with no route on ISP at all. An inside global address from any other range, including a NAT pool built from a separate block, needs a route on the upstream router that points back at the NAT router. With that one route added on ISP, and no change at all to the NAT configuration, the same ping succeeds.

Two more causes sit on the same path. An inbound ACL on the outside interface is checked before the reply is translated back, so it must permit the inside global address, not the private one. And existing translations keep their old mapping until they expire, so clear them after any NAT change before you judge the fix.

EDGE(config)# ip nat inside source static 192.168.10.11 203.0.113.17
EDGE(config)# end

HOST# ping 198.51.100.10 source 192.168.10.11
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 198.51.100.10, timeout is 2 seconds:
Packet sent with a source address of 192.168.10.11 
.....
Success rate is 0 percent (0/5)

EDGE# show ip nat translations
Pro Inside global      Inside local       Outside local      Outside global
icmp 203.0.113.17:10   192.168.10.11:10   198.51.100.10:10   198.51.100.10:10
--- 203.0.113.17       192.168.10.11      ---                ---
--- 203.0.113.5        192.168.10.20      ---                ---

ISP# show ip route 203.0.113.17
% Subnet not in table

ISP(config)# ip route 203.0.113.16 255.255.255.248 203.0.113.1
ISP(config)# end

HOST# ping 198.51.100.10 source 192.168.10.11
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 198.51.100.10, timeout is 2 seconds:
Packet sent with a source address of 192.168.10.11 
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/1 ms

The translation exists and the reply is still lost until the upstream router can route the inside global address. Output captured on Cisco IOL (IOS XE 17.16) in Cisco Modeling Labs.

Match the symptom to the check

Most NAT tickets fit one of these patterns. Each one points to a single place to look first.

  • The router's ping works and the host's does not: this is expected when NAT is broken. Retest from the inside host before changing anything.
  • The table is empty, or holds only static entries, after a host test: check the roles in show ip nat statistics, then the list.
  • One host translates and others do not: the list does not cover the hosts that fail. Static mappings bypass the list.
  • Entries appear but replies do not: follow the inside global address back through the upstream route and any outside ACL.
  • It worked until a change: run clear ip nat translation * and test again before undoing anything.
  • With a NAT pool, the first hosts work and later ones fail: the rule is missing overload, so each translated host holds a whole pool address until its entries expire. The NAT and PAT cheat sheet lists both forms.

Frequently asked questions

Why does ping from the NAT router work when inside hosts cannot reach the internet?

The router sends its own pings from its outside interface address, which is already routable, so the packet is never translated and the reply comes straight back. Inside hosts depend on translation. Test from an inside host, or from a router on the LAN using ping with the source option set to an inside address.

Does the NAT access list block the traffic it does not permit?

No. The list only decides which sources are translated. Traffic from a source the list does not permit is still routed, with its private address intact, so it usually reaches the destination and then loses the reply. To block traffic, apply a separate ACL to an interface.

Does every LAN interface need ip nat inside?

Every interface whose hosts need translation does, including subinterfaces and SVIs used for inter-VLAN routing. NAT ignores an interface with no role, so hosts behind it are forwarded untranslated even when the rule and the list cover their subnet.

Can a routing problem look like a NAT problem?

Often. For traffic leaving the inside, the router makes its routing decision before it translates. With no route to the destination, the packet is dropped before NAT sees it, and the table stays empty. With a route but no return path to the inside global address, entries appear and the replies are lost.

Why does one inside host work while the others fail?

Usually because that host has a static mapping, which does not use the NAT access list, or because the list covers only part of the subnet. Compare the list entries with the exact addresses that fail, and check the list's match counter while those hosts send traffic.

Troubleshoot NAT on a graded lab

Repair NAT on real IOS in CML, then upload the config for a grade.

All NAT labs →

What grading looks like

This is what grading your config looks like

A sample result — 4 of 5 requirements met. This is exactly what you see the moment you submit.

nat-lab-export.yaml — read 3 device configs

Score: 80% — Keep going (4 of 5 checks passed)

  • Passed: ip nat inside set on Gi0/1
  • Passed: ip nat outside set on Gi0/0
  • Passed: NAT pool defined for the public range
  • Passed: Inside host translates to a public address
  • Failed: ACL matches the inside network — wildcard covers the wrong subnet
Solution revealed

The full node-by-node answer key unlocks the moment you submit — so you see exactly what you missed.

Don't just read it — configure it

Reading is step one. Build NAT & PAT on real Cisco IOS and have your own config graded.