The Cisco ASA Modular Policy Framework (MPF) is the configuration model that decides which traffic gets which kind of treatment as it crosses the firewall. It is what binds inspection engines to specific traffic, what applies QoS or connection limits, and what sets timeouts that diverge from the platform defaults. The pieces are three: class-maps (matching traffic), policy-maps (the actions to apply), and service-policies (binding the policy-map to an interface or globally). This article walks the model, the default global policy that ships on every ASA, and how to add custom HTTP, FTP, or ESMTP inspection without breaking what is already working.
This is a Fundamentals article in the Cisco ASA cluster. The companion article on the data-plane side is Cisco ASA Packet Flow, which covers where the inspection engines actually fire in the per-packet pipeline.
The Three MPF Pieces
| Construct | What it does | Example |
|---|---|---|
class-map NAME | Matches traffic. ACL-style criteria, or built-in match default-inspection-traffic. | class-map WEB-CLASS + match access-list WEB-ACL |
policy-map NAME | Lists actions to apply to matched traffic. One or more class entries, each with one or more action lines. | policy-map WEB-POLICY + class WEB-CLASS + inspect http |
service-policy NAME {global | interface NAME} | Activates the policy-map. Either globally (one policy across all interfaces) or per-interface. | service-policy WEB-POLICY interface outside |
One policy-map can have many class entries, and each class can have many actions. Activating the policy-map binds the whole thing at once.
The Default Global Policy
Every fresh ASA ships with a global service-policy already in place. It catches traffic with a built-in class and applies inspection for the standard application-layer protocols.
ASA-PERIM# show running-config policy-map global_policy
policy-map global_policy
class inspection_default
inspect dns preset_dns_map
inspect ftp
inspect h323 h225
inspect h323 ras
inspect ip-options
inspect netbios
inspect rsh
inspect rtsp
inspect skinny
inspect esmtp
inspect sqlnet
inspect sunrpc
inspect tftp
inspect sip
inspect xdmcp
class class-default
user-statistics accounting
!
ASA-PERIM# show running-config service-policy
service-policy global_policy global
Read it as: a class called inspection_default matches a built-in set of "ports we know about for the listed protocols" (DNS on 53, FTP on 21, etc.). The actions are inspect <protocol> for each application-layer engine. The service-policy line binds it globally.
The ASA does not include inspect icmp by default on every release. The lab's running config has it explicitly added (which is why pings come back). Confirm:
ASA-PERIM# show service-policy global | include icmp
Inspect: icmp, packet 1244, lock fail 0, drop 0, reset-drop 0
If the line is missing, add it:
ASA-PERIM(config)# policy-map global_policy
ASA-PERIM(config-pmap)# class inspection_default
ASA-PERIM(config-pmap-c)# inspect icmp
Without inspect icmp, ICMP echo requests leave the firewall but echo replies have no matching conn entry and get dropped. ICMP is the easiest "is the ASA letting traffic through" test, so getting inspection on for it early is worth doing.
What an Inspection Engine Actually Does
Three things, depending on the protocol:
- Stateful tracking for connectionless protocols. ICMP is the canonical example: the ASA pretends ICMP is stateful so echo replies match echo requests.
- Pinhole opening for protocols with negotiated secondary ports. FTP active mode negotiates a data port over the control channel. Without inspection, the data channel hits the perimeter ACL and gets denied; with inspection, the ASA reads the PORT command, opens a temporary pinhole for the negotiated port, and closes it when the session ends.
- Application-layer security checks. The HTTP inspection engine can enforce HTTP method allow-lists, header sanity, URL length limits, and reject malformed requests. The ESMTP engine masks server banners, blocks dangerous SMTP verbs, and enforces RFC compliance on email transactions.
The first category is essential and almost always wanted. The second is required for any application that uses negotiated secondary ports. The third is application-layer firewalling that overlaps with what the next-gen successor (FTD) does in a more flexible model. ASA's application-layer inspection still works but is rarely the central piece of a modern security strategy.
Custom Class-Maps: Match More Specifically
Sometimes the built-in inspection_default class is too broad and you want to apply an action to a narrower slice of traffic. Three matching styles cover almost everything:
! Match by ACL (the most common)
ASA-PERIM(config)# access-list WEB-ACL extended permit tcp any object DMZ-WEB eq https
ASA-PERIM(config)# class-map WEB-CLASS
ASA-PERIM(config-cmap)# match access-list WEB-ACL
!
! Match by port (lighter than an ACL)
ASA-PERIM(config)# class-map SSH-CLASS
ASA-PERIM(config-cmap)# match port tcp eq 22
!
! Match by destination IP only
ASA-PERIM(config)# class-map DMZ-CLASS
ASA-PERIM(config-cmap)# match destination-address dmz 192.168.50.0 255.255.255.0
One class-map can have one match line. For more complex matching (multiple conditions ANDed or ORed), build the matching into an ACL and use match access-list.
Worked Example: Custom HTTP Inspection for the DMZ
Apply HTTP inspection only to traffic destined for the DMZ web server, with custom restrictions (only GET/POST allowed, no PUT/DELETE, max URI length 1024 bytes).
! 1. Define what custom HTTP inspection should do
ASA-PERIM(config)# policy-map type inspect http DMZ-HTTP-MAP
ASA-PERIM(config-pmap)# parameters
ASA-PERIM(config-pmap-p)# protocol-violation action drop-connection log
ASA-PERIM(config-pmap)# match request method PUT
ASA-PERIM(config-pmap-c)# drop-connection log
ASA-PERIM(config-pmap)# match request method DELETE
ASA-PERIM(config-pmap-c)# drop-connection log
ASA-PERIM(config-pmap)# match request uri length gt 1024
ASA-PERIM(config-pmap-c)# drop-connection log
!
! 2. Define the class-map that matches DMZ-bound web traffic
ASA-PERIM(config)# access-list DMZ-WEB-ACL extended permit tcp any object DMZ-WEB eq http
ASA-PERIM(config)# access-list DMZ-WEB-ACL extended permit tcp any object DMZ-WEB eq https
ASA-PERIM(config)# class-map DMZ-WEB-CLASS
ASA-PERIM(config-cmap)# match access-list DMZ-WEB-ACL
!
! 3. Build the policy-map with the inspection action referencing the inspect map
ASA-PERIM(config)# policy-map DMZ-POLICY
ASA-PERIM(config-pmap)# class DMZ-WEB-CLASS
ASA-PERIM(config-pmap-c)# inspect http DMZ-HTTP-MAP
!
! 4. Bind it to the outside interface
ASA-PERIM(config)# service-policy DMZ-POLICY interface outside
The new service-policy on the outside interface coexists with the global one. Per-interface policies evaluate before global; if a packet matches an interface policy class, the interface policy wins and the global is skipped for that packet. If the packet does not match any class on the interface policy, the global policy is consulted next.
Verify the new policy is in place:
ASA-PERIM# show service-policy interface outside
Interface outside:
Service-policy: DMZ-POLICY
Class-map: DMZ-WEB-CLASS
Inspect: http DMZ-HTTP-MAP, packet 87, lock fail 0, drop 0, reset-drop 0
The packet count climbing means the inspection engine is firing. The drop and reset-drop counters tell you whether any real packets are being rejected by the application-layer rules.
Connection Limits and Timeouts
MPF also drives per-flow connection limits and timeouts. Useful for limiting a high-fan-out source from exhausting the conn table, or for shortening the idle timeout on protocols you know are bursty.
ASA-PERIM(config)# policy-map global_policy
ASA-PERIM(config-pmap)# class inspection_default
ASA-PERIM(config-pmap-c)# set connection conn-max 100000
ASA-PERIM(config-pmap-c)# set connection embryonic-conn-max 1000
ASA-PERIM(config-pmap-c)# set connection per-client-max 5000
ASA-PERIM(config-pmap-c)# set connection timeout idle 1:00:00
ASA-PERIM(config-pmap-c)# set connection timeout half-closed 0:10:00
Walk through:
conn-max 100000: cap the total simultaneous conns matching this class. Useful for keeping a single application from hogging the table.embryonic-conn-max 1000: cap on TCP half-open SYNs. The ASA's classic SYN-flood mitigation; below this the ASA proxies the SYN/ACK to validate the source.per-client-max 5000: cap per source IP. Stops a single noisy client from hogging.timeout idle: how long an idle conn lives before the ASA tears it down. Default is 1 hour; tune up for long-lived idle protocols.timeout half-closed: TCP FIN/FIN-WAIT timeout. Default is 10 minutes; tune down to reclaim conns faster after one side has gone away.
QoS via MPF
The ASA's QoS support is narrower than IOS - it does priority queuing and policing on selected traffic, not full diffserv-style queuing.
ASA-PERIM(config)# class-map VOICE-CLASS
ASA-PERIM(config-cmap)# match dscp ef
!
ASA-PERIM(config)# policy-map QOS-POLICY
ASA-PERIM(config-pmap)# class VOICE-CLASS
ASA-PERIM(config-pmap-c)# priority
!
ASA-PERIM(config)# service-policy QOS-POLICY interface outside
Voice traffic (DSCP EF) gets the priority queue on the outside interface. Most ASA deployments do not need this because the upstream router or WAN edge handles QoS; the ASA's job is forwarding and security, not shaping.
Verification Commands
| Command | What it tells you |
|---|---|
show service-policy global | What classes are on the global policy and their per-class hit/drop counters. |
show service-policy interface NAME | The interface-bound service-policy and its counters. |
show running-config policy-map | All policy-map definitions in the running config. |
show running-config class-map | All class-map definitions. |
show running-config service-policy | The bindings (which policy-map is bound where). |
show conn protocol tcp | Active TCP conns; useful to see what inspection has tracked. |
show inspect http | Per-engine internal counters and stats (replace http with the engine you care about). |
Common Traps
Removing the global policy without replacing it. If you do no service-policy global_policy global without binding a replacement, you lose all default inspection. ICMP stops working, FTP active stops working, your DNS bonus features evaporate. If you are migrating to per-interface policies, build the new ones first and remove the global last - or keep the global and let per-interface policies layer on top.
Conflicting per-interface and global classes. Per-interface wins over global, even for the same class name. If you build an interface policy that covers inspection_default, you have just disabled the global engines for traffic on that interface. Be explicit about which interface policies cover which classes.
HTTP inspection breaking valid traffic. The default HTTP inspect map is permissive. Custom maps with strict rules (no PUT, max URI 256) are easy to write and easy to over-tighten. Test with the actual application before binding to a production interface; the application-layer reset comes back as a connection reset to the client, with no obvious clue at the client end.
Forgetting to count. Every class entry in a policy-map with no actions does nothing useful. The show service-policy output still shows it, with packet counters at zero or a constant. Watch for a class with growing matches but no actions - that is configuration that has no effect.
Key Takeaways
The Cisco ASA MPF is class-map matches traffic, policy-map applies actions, service-policy binds the policy globally or to an interface. The default global_policy ships with most application-layer inspections enabled and the lab adds inspect icmp so ping replies survive the firewall. Custom inspection adds rules for HTTP, FTP, ESMTP, and other protocols where you want application-layer security; per-interface policies override global for matching classes.
For where the inspection engines fire in the per-packet pipeline, see Cisco ASA Packet Flow. For confirmation that a specific flow is hitting an inspection class, run show service-policy and watch the packet counter for the class climb. The full reading order is on the Cisco ASA pillar.