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
| Zone | Interface | Subnet | Security level | Role |
|---|---|---|---|---|
| inside | GigabitEthernet0/1 | 10.10.0.0/24 | 100 | User LAN, default gateway 10.10.0.254 (the ASA) |
| dmz | GigabitEthernet0/2 | 192.168.50.0/24 | 50 | Public-facing servers; DMZ-WEB at 192.168.50.10 |
| outside | GigabitEthernet0/0 | 203.0.113.0/30 | 0 | Point-to-point link to the ISP, ASA at .2, ISP at .1 |
| NAT pool | (virtual) | 198.51.100.0/24 | n/a | 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 Cisco1@3
!
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
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.