Cisco ASA Inside/Outside/DMZ Configuration Walkthrough

The classic three-zone ASA build end to end on an ASAv 9.23: inside, outside, and DMZ security levels, outbound NAT, inbound web publishing, ACLs in both directions, and the show output that proves traffic flows.

Cisco ASA Inside/Outside/DMZ Configuration Walkthrough - PingLabz ASA article title card

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

inside
InterfaceGigabitEthernet0/1
Subnet10.10.0.0/24
Security level100
Role
User LAN, default gateway 10.10.0.254 (the ASA)
dmz
InterfaceGigabitEthernet0/2
Subnet192.168.50.0/24
Security level50
Role
Public-facing servers; DMZ-WEB at 192.168.50.10
outside
InterfaceGigabitEthernet0/0
Subnet203.0.113.0/30
Security level0
Role
Point-to-point link to the ISP, ASA at .2, ISP at .1
NAT pool
Interface(virtual)
Subnet198.51.100.0/24
Security leveln/a
Role
Public block for static NAT (DMZ-WEB to .10)

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 / roleASA interface and addressEndpointSecurity level / translation
insideGigabitEthernet0/0, 10.61.10.1/24inside-client, 10.61.10.10/24100; interface PAT to 192.0.2.1
outsideGigabitEthernet0/1, 192.0.2.1/24outside-client, 192.0.2.2/240; client requests 192.0.2.50:80
dmzGigabitEthernet0/2, 10.61.50.1/24dmz-web, 10.61.50.10/2450; 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 outside

The 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.8

Inside-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 ns

UN-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 www

Return 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 loss

Before 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:00

The 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)/NA

Read 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 64

That 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 www

Exit 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.8

Fault 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.50

Exit 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/24

The 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)/NA

There 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 kernel

Its 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

CheckFault A: HTTP permit missingFault B: static NAT missing
Outside curlTimeout; exit 28Timeout; exit 28
Ping to published address2/2 replies0/2 replies
Static NAT / xlateBoth presentDMZ static rule and xlate absent
First trace phaseStatic UN-NAT ALLOW to real DMZ addressROUTE-LOOKUP; mapped address stays on outside
Selected egressdmzoutside
Rule that dropsNamed OUTSIDE_IN explicit denyImplicit Rule, outside-to-outside
Final drop reason(acl-drop)(acl-drop)
Named ACL during real probesICMP 0→2; deny 0→4HTTP / ICMP / deny unchanged at 1 / 2 / 5
DMZ captureFour ICMP packets; no TCPZero packets
RepairRestore HTTP permit before denyRestore 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.50

Exit 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.8

Inside-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 ns

Save 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:

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.

Read next