The classic Cisco ASA topology is three zones with three security levels: inside (your trusted users at level 100), outside (the internet at level 0), and a DMZ (your public-facing servers at level 50, somewhere in the middle). This article walks the entire build end to end on the lab's ASAv 9.23 - interfaces, addressing, security levels, NAT for outbound users, NAT for inbound web publishing, ACLs in both directions, and the show-output that proves traffic is actually flowing. The result is a small but realistic perimeter you could drop into a branch office tomorrow.
This is one of the longer Fundamentals walkthroughs in the Cisco ASA cluster. It assumes you have already bootstrapped the ASA from CLI and read the security-levels primer; everything else gets shown here.
Topology and Address Plan
Three real interfaces, three zones, three security levels. The numeric levels (100/50/0) drive the implicit allow/deny matrix between zones - high to low is permitted by default, low to high requires an explicit ACL.
Step 1: Interface Configuration
ASA-PERIM(config)# interface GigabitEthernet0/0
ASA-PERIM(config-if)# description Outside-to-ISP
ASA-PERIM(config-if)# nameif outside
ASA-PERIM(config-if)# security-level 0
ASA-PERIM(config-if)# ip address 203.0.113.2 255.255.255.252
ASA-PERIM(config-if)# no shutdown
!
ASA-PERIM(config)# interface GigabitEthernet0/1
ASA-PERIM(config-if)# description Inside-to-LAN
ASA-PERIM(config-if)# nameif inside
ASA-PERIM(config-if)# security-level 100
ASA-PERIM(config-if)# ip address 10.10.0.254 255.255.255.0
ASA-PERIM(config-if)# no shutdown
!
ASA-PERIM(config)# interface GigabitEthernet0/2
ASA-PERIM(config-if)# description DMZ-Servers
ASA-PERIM(config-if)# nameif dmz
ASA-PERIM(config-if)# security-level 50
ASA-PERIM(config-if)# ip address 192.168.50.1 255.255.255.0
ASA-PERIM(config-if)# no shutdown
Verify with show interface ip brief:
ASA-PERIM# show interface ip brief
Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0 203.0.113.2 YES manual up up
GigabitEthernet0/1 10.10.0.254 YES manual up up
GigabitEthernet0/2 192.168.50.1 YES manual up up
Management0/0 unassigned YES unset administratively down down
Three data interfaces up. See interfaces and VLAN trunks if any of these are not coming up the way you expect.
Step 2: Default Route to the ISP
ASA-PERIM(config)# route outside 0.0.0.0 0.0.0.0 203.0.113.1 1
Verify the routing table now has the connected subnets plus the default:
ASA-PERIM# show route
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
E1 - OSPF external type 1, E2 - OSPF external type 2, V - VPN
i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
ia - IS-IS inter area, * - candidate default, U - per-user static route
o - ODR, P - periodic downloaded static route, + - replicated route
SI - Static InterVRF
Gateway of last resort is 203.0.113.1 to network 0.0.0.0
S* 0.0.0.0 0.0.0.0 [1/0] via 203.0.113.1, outside
C 10.10.0.0 255.255.255.0 is directly connected, inside
L 10.10.0.254 255.255.255.255 is directly connected, inside
C 192.168.50.0 255.255.255.0 is directly connected, dmz
L 192.168.50.1 255.255.255.255 is directly connected, dmz
C 203.0.113.0 255.255.255.252 is directly connected, outside
L 203.0.113.2 255.255.255.255 is directly connected, outside
The default route via outside (the S* line) is the gateway of last resort. Connected and local routes for each of the three zones are present.
Step 3: Outbound PAT for Inside Users
Inside users at 10.10.0.0/24 cannot reach the internet directly because their addresses are RFC1918. The ASA needs to NAT them to the outside interface IP. The simplest pattern is Auto NAT on a network object:
ASA-PERIM(config)# object network INSIDE-NET
ASA-PERIM(config-network-object)# subnet 10.10.0.0 255.255.255.0
ASA-PERIM(config-network-object)# nat (inside,outside) dynamic interface
Read it as: "anything sourced from 10.10.0.0/24 on inside, headed toward outside, gets PATed to the outside interface IP." That single rule covers all outbound user traffic. The security-level 100 to 0 rule lets the traffic pass without an explicit ACL.
Step 4: Publish DMZ-WEB Inbound
The DMZ web server lives at 192.168.50.10. It needs a public IP (we will use 198.51.100.10) and an inbound ACL because traffic flowing low-to-high (outside 0 to dmz 50) is denied by default.
The static NAT, again with an Auto NAT pattern:
ASA-PERIM(config)# object network DMZ-WEB
ASA-PERIM(config-network-object)# host 192.168.50.10
ASA-PERIM(config-network-object)# nat (dmz,outside) static 198.51.100.10
The ACL that opens 80, 443, and ICMP echo:
ASA-PERIM(config)# access-list OUTSIDE_IN extended permit tcp any object DMZ-WEB eq www
ASA-PERIM(config)# access-list OUTSIDE_IN extended permit tcp any object DMZ-WEB eq https
ASA-PERIM(config)# access-list OUTSIDE_IN extended permit icmp any object DMZ-WEB
ASA-PERIM(config)# access-list OUTSIDE_IN extended deny ip any any log informational interval 300
ASA-PERIM(config)# access-group OUTSIDE_IN in interface outside
Two important details. First, the ACL references object DMZ-WEB, which the ASA resolves to 192.168.50.10 (the real address) because that is what is in the object. The ASA evaluates ACLs against post-NAT real addresses on inbound, which is the modern (8.3+) behavior. Second, the explicit deny ip any any log at the bottom does the same thing as the implicit deny, but it generates a syslog every time it fires. That is the line we use to debug "why is traffic getting blocked" issues, as covered in ACL troubleshooting.
Step 5: ICMP Inspection (For Pings to Come Back)
ICMP is connectionless, which means without inspection an outbound echo request leaves the firewall but the inbound echo reply gets dropped (no matching conn entry). The default global policy includes inspect icmp, which makes ICMP behave like a stateful protocol on the ASA. If ping does not work from inside out, this is one of the first things to check.
ASA-PERIM# show service-policy global | include icmp
Inspect: icmp, packet 1244, lock fail 0, drop 0, reset-drop 0
The packet counter climbing tells you ICMP inspection is firing. See inspection engines for the full MPF model.
Step 6: End-to-End Verification
Now exercise the policy from both directions.
Outbound: inside-host to 8.8.8.8
Generate a flow from inside-host on the inside subnet:
inside-host:~# ping -c 3 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=51 time=23.4 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=51 time=24.1 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=51 time=23.8 ms
And confirm on the ASA:
ASA-PERIM# show xlate count
14 in use, 16 most used
ASA-PERIM# show xlate local 10.10.0.50
Flags: D - DNS, e - extended, I - identity, i - dynamic, r - portmap,
s - static, T - twice, N - net-to-net
ICMP PAT from inside:10.10.0.50 1/12345 to outside:203.0.113.2 1/45678 flags ri idle 0:00:01 timeout 0:00:30
The outbound ping created an ICMP xlate entry. The inside source was PATed to the outside interface IP (203.0.113.2). When the echo reply comes back, the ASA matches it to the xlate, NATs the destination back to 10.10.0.50, and forwards it.
Inbound: 8.8.8.8 to DMZ-WEB on https
From outside, hit the public IP:
outside-tester:~# curl -sI -k https://198.51.100.10
HTTP/1.1 200 OK
Server: nginx/1.20.2
And on the ASA:
ASA-PERIM# show conn address 192.168.50.10
14 in use, 16 most used
TCP outside 8.8.8.8:51234 dmz 192.168.50.10:443, idle 0:00:01, bytes 1132, flags UIO
ASA-PERIM# show access-list OUTSIDE_IN | include hitcnt
access-list OUTSIDE_IN line 3 extended permit tcp any object DMZ-WEB eq https (hitcnt=2)
The conn entry shows the established TCP flow. The hit counter on line 3 of OUTSIDE_IN ticks up. The U flag in the conn flags means "up," meaning the TCP three-way handshake completed - which it could only do because both directions of the flow (the SYN from outside, the SYN/ACK from DMZ-WEB) made it through the firewall.
Cross-zone: DMZ to inside (Should Be Blocked)
By default, traffic from a lower security level (DMZ at 50) to a higher one (inside at 100) is denied. Test it:
dmz-host:~# ping -c 1 10.10.0.50
PING 10.10.0.50 (10.10.0.50) 56(84) bytes of data.
^C
--- 10.10.0.50 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss
ASA-PERIM# show asp drop frame | include acl-drop
Flow is denied by configured rule (acl-drop) 1612
The acl-drop counter ticks up because the implicit deny on the dmz interface (no ACL bound, default deny low-to-high) blocked the packet. show asp drop counters covers what each reason actually means.
If you do need DMZ-to-inside reachability for a specific application (a backup server pulling from a database, for example), add an explicit ACL on the dmz interface that permits exactly that flow.
The Full Working Config in One Block
Everything from this article in a single paste-able config:
!
hostname ASA-PERIM
domain-name pinglabz.lab
enable password <PASSWORD>
!
interface GigabitEthernet0/0
description Outside-to-ISP
nameif outside
security-level 0
ip address 203.0.113.2 255.255.255.252
no shutdown
interface GigabitEthernet0/1
description Inside-to-LAN
nameif inside
security-level 100
ip address 10.10.0.254 255.255.255.0
no shutdown
interface GigabitEthernet0/2
description DMZ-Servers
nameif dmz
security-level 50
ip address 192.168.50.1 255.255.255.0
no shutdown
!
route outside 0.0.0.0 0.0.0.0 203.0.113.1 1
!
object network INSIDE-NET
subnet 10.10.0.0 255.255.255.0
nat (inside,outside) dynamic interface
!
object network DMZ-WEB
host 192.168.50.10
nat (dmz,outside) static 198.51.100.10
!
access-list OUTSIDE_IN extended permit tcp any object DMZ-WEB eq www
access-list OUTSIDE_IN extended permit tcp any object DMZ-WEB eq https
access-list OUTSIDE_IN extended permit icmp any object DMZ-WEB
access-list OUTSIDE_IN extended deny ip any any log informational interval 300
access-group OUTSIDE_IN in interface outside
!
class-map inspection_default
match default-inspection-traffic
policy-map global_policy
class inspection_default
inspect icmp
service-policy global_policy global
!
end
write memory
Lab: Missing ACL vs Missing Static NAT
Removing either the HTTP permit or the static NAT made the published DMZ website time out from outside. In this lab, both faults ended with the same packet-tracer drop reason: (acl-drop). The difference was in the phases above it.
This separate four-node test, built in my home lab, ran on ASAv 9.24(1). It uses the same inside/outside/DMZ design as the build above, with a different address plan and a Python HTTP server on TCP/80. The outside client stands in for an internet host; these tests do not establish public Internet or HTTPS reachability.
Address Plan for This Lab
| Zone / role | ASA interface and address | Endpoint | Security level / translation |
|---|---|---|---|
| inside | GigabitEthernet0/0, 10.61.10.1/24 | inside-client, 10.61.10.10/24 | 100; interface PAT to 192.0.2.1 |
| outside | GigabitEthernet0/1, 192.0.2.1/24 | outside-client, 192.0.2.2/24 | 0; client requests 192.0.2.50:80 |
| dmz | GigabitEthernet0/2, 10.61.50.1/24 | dmz-web, 10.61.50.10/24 | 50; static NAT to 192.0.2.50 |
The endpoints use eth1 for lab traffic. Inside-to-outside and inside-to-DMZ routes point through 10.61.10.1; the server's return route to the outside subnet points through 10.61.50.1. The ASA's outside default route points to the test peer at 192.0.2.2.
One detail matters to the fault test: the outside client has a host route for 192.0.2.50/32 via 192.0.2.1 on eth1. That keeps requests arriving at the ASA even when the static NAT is removed. Otherwise, a client on the same subnet could stop at an unanswered ARP request, and we would be testing a different failure.
With interfaces and routes in place, this is the NAT and outside ACL configuration used for the test, entered in ASA configuration mode:
object network INSIDE_NET
subnet 10.61.10.0 255.255.255.0
nat (inside,outside) dynamic interface
object network DMZ_WEB
host 10.61.50.10
nat (dmz,outside) static 192.0.2.50
access-list OUTSIDE_IN extended permit tcp any host 10.61.50.10 eq www
access-list OUTSIDE_IN extended permit icmp any host 10.61.50.10 echo
access-list OUTSIDE_IN extended deny ip any any log
access-group OUTSIDE_IN in interface outsideThe HTTP permit names the real server, 10.61.50.10. The client connects to the mapped address, 192.0.2.50. ICMP inspection was explicitly configured under the global policy so echo replies could return.
Verify the Working Baseline
Start with the server listening on port 80. From a shell on outside-client, run curl --interface eth1 --noproxy "*" --connect-timeout 4 --max-time 6 -sS -i http://192.0.2.50/. The recorded response after baseline route verification began:
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.14.8Inside-client also reached the outside HTTP service at 192.0.2.2 and the DMZ HTTP service at 10.61.50.10, both with HTTP 200. Its ping to the outside peer passed 2/2. Those checks establish working outbound PAT and inside-to-DMZ forwarding before either fault.
At the ASA privileged prompt, run ping outside 192.0.2.2 and ping dmz 10.61.50.10 to warm ARP, then simulate the inbound request. These are excerpts from the successful baseline trace:
asa# packet-tracer input outside tcp 192.0.2.2 41001 192.0.2.50 80
packet-tracer input outside tcp 192.0.2.2 41001 192.0.2.50 80
Phase: 1
Type: UN-NAT
Subtype: static
Result: ALLOW
Elapsed time: 48068 ns
Config:
object network DMZ_WEB
nat (dmz,outside) static 192.0.2.50
Additional Information:
NAT divert to egress interface dmz
Untranslate 192.0.2.50/80 to 10.61.50.10/80
Phase: 2
Type: ACCESS-LIST
Subtype:
Result: ALLOW
Elapsed time: 8274 ns
Config:
access-group OUTSIDE_IN in interface outside
access-list OUTSIDE_IN extended permit tcp any host 10.61.50.10 eq www
...
Result:
input-interface: outside
input-status: up
input-line-status: up
output-interface: dmz
output-status: up
output-line-status: up
Action: allow
Time Taken: 163904 nsUN-NAT changes the destination to 10.61.50.10 and selects dmz; the HTTP permit allows it. The final verdict is also allow. Use the packet-tracer guide for the other phases, but keep the real curl test alongside the simulation.
For the fault comparisons, simultaneous 16-second tcpdump captures ran on eth1 at outside-client and dmz-web, filtered to arp or icmp or tcp port 80. Each fault used a new curl attempt followed by ping -c 2 -W 1 192.0.2.50 from outside-client. The capture counts below refer to those windows; all captures reported zero packets dropped by the kernel. Command and output excerpts retain the recorded text; ... marks omitted lines, and [exit_status=...] is the recorder's command status.
Fault A: Remove Only the HTTP ACL Permit
Leave static NAT in place. At the ASA, enter configuration mode and remove the TCP/80 permit:
no access-list OUTSIDE_IN extended permit tcp any host 10.61.50.10 eq wwwReturn to privileged EXEC with end, then run clear conn address 10.61.50.10 so an existing connection cannot mask the policy change. Repeat the outside curl and ping tests. HTTP failed, while ping still worked:
curl: (28) Connection timed out after 4001 milliseconds
[exit_status=28]
...
--- 192.0.2.50 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet lossBefore running packet-tracer, show access-list OUTSIDE_IN, show nat detail and show xlate gave these excerpts:
access-list OUTSIDE_IN; 2 elements; name hash: 0xe01d8199
access-list OUTSIDE_IN line 1 extended permit icmp any host 10.61.50.10 echo (hitcnt=2) (Last Hit=06:46:15 UTC Oct 17 2026)
access-list OUTSIDE_IN line 2 extended deny ip any any log informational interval 300 (hitcnt=4) (Last Hit=06:46:13 UTC Oct 17 2026)
...
Auto NAT Policies (Section 2)
1 (dmz) to (outside) source static DMZ_WEB 192.0.2.50
translate_hits = 0, untranslate_hits = 11
Source - Origin: 10.61.50.10/32, Translated: 192.0.2.50/32
...
NAT from dmz:10.61.50.10 to outside:192.0.2.50
flags s idle 0:00:12 timeout 0:00:00The HTTP permit is gone. During the real probes, the ICMP permit rose from 0 to 2 hits and the explicit deny from 0 to 4. The static mapping and its xlate remained, and its untranslate counter rose from 5 to 11. Translation is still happening; the missing HTTP permit is the fault.
Run the same detailed trace that will be used for Fault B:
asa# packet-tracer input outside tcp 192.0.2.2 41000 192.0.2.50 80 detailed
packet-tracer input outside tcp 192.0.2.2 41000 192.0.2.50 80 detailed
Phase: 1
Type: UN-NAT
Subtype: static
Result: ALLOW
Elapsed time: 87862 ns
Config:
object network DMZ_WEB
nat (dmz,outside) static 192.0.2.50
Additional Information:
NAT divert to egress interface dmz
Untranslate 192.0.2.50/80 to 10.61.50.10/80
Phase: 2
Type: ACCESS-LIST
Subtype: log
Result: DROP
Elapsed time: 10769 ns
Config:
access-group OUTSIDE_IN in interface outside
access-list OUTSIDE_IN extended deny ip any any log
...
Result:
input-interface: outside
input-status: up
input-line-status: up
output-interface: dmz
output-status: up
output-line-status: up
Action: drop
Time Taken: 98631 ns
Drop-reason: (acl-drop) Flow is denied by configured rule, Drop-location: frame snp_classify_table_lookup:6167 flow (NA)/NARead those phases in order. Static UN-NAT allows the translation toward dmz, then the named OUTSIDE_IN catch-all deny drops HTTP. The final acl-drop agrees with that path. The ACL troubleshooting walkthrough covers checking the matched line and the real-address permit.
The outside capture contained four unanswered TCP SYNs plus two successful ping exchanges. On the DMZ side, the complete packet list contained only the four ICMP packets:
2026-10-17 06:46:15.171197 IP 192.0.2.2 > 10.61.50.10: ICMP echo request, id 90, seq 0, length 64
2026-10-17 06:46:15.171326 IP 10.61.50.10 > 192.0.2.2: ICMP echo reply, id 90, seq 0, length 64
2026-10-17 06:46:16.169945 IP 192.0.2.2 > 10.61.50.10: ICMP echo request, id 90, seq 1, length 64
2026-10-17 06:46:16.170003 IP 10.61.50.10 > 192.0.2.2: ICMP echo reply, id 90, seq 1, length 64That is why a successful ping is useful here but does not prove the website works. ICMP crossed the same static mapping; TCP/80 never reached the server.
Restore the HTTP permit at line 1, ahead of the explicit deny, in ASA configuration mode:
access-list OUTSIDE_IN line 1 extended permit tcp any host 10.61.50.10 eq wwwExit configuration mode and rerun curl. It returned HTTP 200 before the next fault was introduced:
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.14.8Fault B: Remove Only the Static NAT
Keep the restored HTTP and ICMP permits. At privileged EXEC, run clear conn address 10.61.50.10 again, then enter configuration mode. Under the server object, remove its static NAT:
asa(config)# object network DMZ_WEB
object network DMZ_WEB
asa(config-network-object)# no nat (dmz,outside) static 192.0.2.50
no nat (dmz,outside) static 192.0.2.50Exit configuration mode and repeat the same outside probes. This time both HTTP and ping failed:
curl: (28) Connection timed out after 4001 milliseconds
[exit_status=28]
...
--- 192.0.2.50 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss
[exit_status=1]The pre-trace ACL and Auto NAT excerpts were:
access-list OUTSIDE_IN; 3 elements; name hash: 0xe01d8199
access-list OUTSIDE_IN line 1 extended permit tcp any host 10.61.50.10 eq www (hitcnt=1) (Last Hit=06:46:35 UTC Oct 17 2026)
access-list OUTSIDE_IN line 2 extended permit icmp any host 10.61.50.10 echo (hitcnt=2) (Last Hit=06:46:15 UTC Oct 17 2026)
access-list OUTSIDE_IN line 3 extended deny ip any any log informational interval 300 (hitcnt=5) (Last Hit=06:46:28 UTC Oct 17 2026)
...
Auto NAT Policies (Section 2)
1 (inside) to (outside) source dynamic INSIDE_NET interface
translate_hits = 7, untranslate_hits = 0
Source - Origin: 10.61.10.0/24, Translated: 192.0.2.1/24The HTTP permit is present, but the DMZ static rule is absent from Section 2. show xlate also no longer contained the 10.61.50.10 to 192.0.2.50 static entry. The named ACL's HTTP, ICMP and deny counts stayed at 1 / 2 / 5, exactly where they stood before these failed probes.
Now compare the complete failed trace with Fault A:
asa# packet-tracer input outside tcp 192.0.2.2 41000 192.0.2.50 80 detailed
packet-tracer input outside tcp 192.0.2.2 41000 192.0.2.50 80 detailed
Phase: 1
Type: ROUTE-LOOKUP
Subtype: No ECMP load balancing
Result: ALLOW
Elapsed time: 43340 ns
Config:
Additional Information:
Destination is locally connected. No ECMP load balancing.
Found next-hop 192.0.2.50 using egress ifc outside
Phase: 2
Type: ACCESS-LIST
Subtype:
Result: DROP
Elapsed time: 8930 ns
Config:
Implicit Rule
Additional Information:
Forward Flow based lookup yields rule:
in id=0x7f0b35573f00, priority=111, domain=permit, deny=true
hits=6, user_data=0x0000000000000000, cs_id=0x0, flags=0x4000, protocol=0
src ip/id=0.0.0.0, mask=0.0.0.0, port=0, tag=any
dst ip/id=0.0.0.0, mask=0.0.0.0, port=0, tag=any
dscp=0x0, input_ifc=outside, output_ifc=outside
Result:
input-interface: outside
input-status: up
input-line-status: up
output-interface: outside
output-status: up
output-line-status: up
Action: drop
Time Taken: 52270 ns
Drop-reason: (acl-drop) Flow is denied by configured rule, Drop-location: frame snp_classify_table_lookup:6167 flow (NA)/NAThere is no UN-NAT phase. The destination stays 192.0.2.50, which the ASA sees on its connected outside subnet. Route lookup therefore selects outside, and an Implicit Rule drops the outside-to-outside flow. The final reason is still (acl-drop), even though restoring another ACL permit would not restore the missing destination translation.
The outside capture proves the requests reached the firewall's link: four TCP SYNs and two echo requests, with no replies. The simultaneous DMZ capture was empty:
0 packets captured
0 packets received by filter
0 packets dropped by kernelIts saved pcap was 24 bytes, just the file header, and tcpdump read it back successfully with no packet rows. There was no DMZ TCP or ICMP traffic in that window. Combined with the missing NAT policy and the outside egress in packet-tracer, that separates this fault from Fault A.
Compare the Two Failures
| Check | Fault A: HTTP permit missing | Fault B: static NAT missing |
|---|---|---|
| Outside curl | Timeout; exit 28 | Timeout; exit 28 |
| Ping to published address | 2/2 replies | 0/2 replies |
| Static NAT / xlate | Both present | DMZ static rule and xlate absent |
| First trace phase | Static UN-NAT ALLOW to real DMZ address | ROUTE-LOOKUP; mapped address stays on outside |
| Selected egress | dmz | outside |
| Rule that drops | Named OUTSIDE_IN explicit deny | Implicit Rule, outside-to-outside |
| Final drop reason | (acl-drop) | (acl-drop) |
| Named ACL during real probes | ICMP 0→2; deny 0→4 | HTTP / ICMP / deny unchanged at 1 / 2 / 5 |
| DMZ capture | Four ICMP packets; no TCP | Zero packets |
| Repair | Restore HTTP permit before deny | Restore static NAT under DMZ_WEB |
These counter snapshots were taken before each fault's packet-tracer run. Simulations can also affect counters, so do not mix their hits into the real-probe comparison. ASP drop counters are cumulative too; a rising acl-drop total alone cannot distinguish these faults.
The missing-NAT trace is specific to this address plan and policy: the untranslated destination is connected outside, and the client has a route that still delivers the test packets to the ASA. A different topology can fail at a different phase. Follow the selected translation, egress interface and matched rule rather than expecting a particular drop string.
Restore and Verify Recovery
With the HTTP permit already restored, re-add the original mapping under DMZ_WEB in ASA configuration mode:
asa(config)# object network DMZ_WEB
object network DMZ_WEB
asa(config-network-object)# nat (dmz,outside) static 192.0.2.50
nat (dmz,outside) static 192.0.2.50Exit configuration mode, ping both directly attached peers from the ASA, then repeat the outside curl test. The restored response began:
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.14.8Inside-to-outside HTTP and inside-to-DMZ HTTP also returned 200 again, and the inside-to-outside ping passed 2/2. The restored DMZ capture contained the inbound HTTP handshake and HTTP 200 response. The detailed inbound trace again translated to the real server, matched the HTTP permit and allowed the flow:
Phase: 1
Type: UN-NAT
Subtype: static
Result: ALLOW
Elapsed time: 62646 ns
Config:
object network DMZ_WEB
nat (dmz,outside) static 192.0.2.50
Additional Information:
NAT divert to egress interface dmz
Untranslate 192.0.2.50/80 to 10.61.50.10/80
Phase: 2
Type: ACCESS-LIST
Subtype:
Result: ALLOW
Elapsed time: 10638 ns
Config:
access-group OUTSIDE_IN in interface outside
access-list OUTSIDE_IN extended permit tcp any host 10.61.50.10 eq www
...
Result:
input-interface: outside
input-status: up
input-line-status: up
output-interface: dmz
output-status: up
output-line-status: up
Action: allow
Time Taken: 211578 nsSave the working configuration after verification:
asa# write memory
write memory
Building configuration...
Cryptochecksum: 56534fea 29bcfc11 1abc295e ab6fb077
11854 bytes copied in 0.210 secs
[OK]The saved checksum matched the corrected baseline, and the final running configurations matched. This test exercised one missing permit and one missing static mapping. If both are present but the wrong translation wins, continue with stale manual NAT overriding a static publish and ASA NAT table order to check which rule is selected first.
What This Does and Does Not Cover
This baseline gets a working three-zone perimeter with outbound PAT and inbound DMZ publishing. It does not yet have:
- Authentication for users (covered in management hardening and the AAA article).
- VPN access (see the AnyConnect SSL and IKEv2 walkthroughs).
- HA failover (see active/standby).
- Logging beyond the implicit defaults (see syslog).
Each of those layers builds on the same baseline. Get the three zones, the routes, the NAT, and the ACLs right first; everything else assumes you have done so.
Key Takeaways
The classic inside/outside/DMZ ASA build is a small set of ordered steps: bring up three named interfaces with their security levels, add a default route, configure PAT for the inside subnet, add a static NAT for the DMZ server, and write an inbound ACL for the public-facing services. From there, show xlate tells you outbound NAT is firing, show conn tells you flows exist, and show access-list tells you which ACL line is matching.
The implicit security-level matrix does most of the work: outbound (high to low) is permitted by default, inbound (low to high) requires an explicit ACL. Read the security-levels article if any of that feels arbitrary; it sets up everything else.
Next: Cisco ASA Static Routing and Default Route Configuration covers tracked routes, route metrics, and multi-WAN failover. The full reading order is on the Cisco ASA pillar.