Skip to content

SNMP & Protocols

SNMP ports 161 and 162: firewall rules that let polling and traps through

SNMP uses UDP port 161 on the device being monitored (the agent) and UDP port 162 on the machine that receives traps (the manager). Your poller sends requests to 161 and gets replies back from it; devices send traps and informs to 162. So the SNMP port rules come in two halves: allow UDP 161 into every agent from your pollers only, and allow UDP 162 into your trap receiver from your devices only. Everything else in this piece is the detail of writing those two rules correctly on the firewalls you are likely to run.

SNMP port numbers at a glance

Port Transport Listens on Carries
161 UDP The agent: switch, router, UPS card, server running snmpd GET, GETNEXT, GETBULK and SET requests; replies go back from 161
162 UDP The manager: snmptrapd or your monitoring server Traps and informs; inform acknowledgements go back from 162
161, 162 TCP Rarely anything SNMP over TCP exists as a standard mapping, but few devices offer it
10161 TCP or UDP Agents that support it SNMP over TLS or DTLS (RFC 6353)
10162 TCP or UDP Receivers that support it Notifications over TLS or DTLS
705 TCP The master agent, normally on localhost AgentX subagents (RFC 2741); never needs to cross a firewall

SNMPv3 does not get ports of its own. It runs on the same 161 and 162 as v1 and v2c, including the engine discovery exchange at the start of a v3 session, so the rules below cover all three versions.

Which way the SNMP traffic flows

Get the direction right and the rules write themselves.

From To Destination port What it is
Poller, from a random high port Agent UDP 161 A poll or a walk
Agent, from 161 Poller, to that same high port (reply) The answer
Agent, usually from a random high port Trap receiver UDP 162 A trap or inform
Trap receiver, from 162 Agent (reply) The acknowledgement, for informs only

A stateful firewall (anything with connection tracking: iptables with conntrack, nftables, ufw, firewalld, pfSense, OPNsense, cloud security groups) matches the replies automatically, so you only write the two inbound rules. A stateless router ACL needs the reply direction spelled out as well: UDP from source port 161 back to the pollers, and UDP from source port 162 back to the devices if you use informs.

Notice the third row. Traps do not reliably come from port 161. Some devices use it as the source port, most pick a random one. Match traps on the destination port only.

In the examples, the pollers are 10.99.0.5 and 10.99.0.6, the trap receiver is 10.99.0.5, and the devices live in 10.20.0.0/16. Each set opens 161 on a Linux server that runs snmpd, and 162 on the box that runs snmptrapd.

iptables

# Replies to anything this host started, and loopback
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -i lo -j ACCEPT

# Agent: polls from the two pollers only
sudo iptables -A INPUT -p udp -s 10.99.0.5 --dport 161 -j ACCEPT
sudo iptables -A INPUT -p udp -s 10.99.0.6 --dport 161 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 161 -j DROP

# Trap receiver only: traps and informs from the device range
sudo iptables -A INPUT -p udp -s 10.20.0.0/16 --dport 162 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 162 -j DROP

Order matters. -A appends, and if the chain already ends in a catch-all REJECT, your new rules sit behind it and never match. List the chain with sudo iptables -L INPUT -n --line-numbers and insert at a position with -I INPUT 4 instead. Rules added this way vanish on reboot; on Debian and Ubuntu, the iptables-persistent package saves them with sudo netfilter-persistent save. IPv6 needs the same rules again with ip6tables.

nftables

On Debian the nftables service loads /etc/nftables.conf at boot. (Red Hat systems keep theirs under /etc/sysconfig, though firewalld usually owns nftables there; see below.) A minimal file for a host that is both agent and trap receiver:

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
    set snmp_pollers {
        type ipv4_addr
        elements = { 10.99.0.5, 10.99.0.6 }
    }

    chain input {
        type filter hook input priority 0; policy drop;
        ct state established,related accept
        ct state invalid drop
        iif "lo" accept
        meta l4proto { icmp, ipv6-icmp } accept
        ip saddr 10.99.0.0/24 tcp dport 22 accept
        ip saddr @snmp_pollers udp dport 161 accept
        ip saddr 10.20.0.0/16 udp dport 162 accept
    }
}

Check it, then load it:

sudo nft -c -f /etc/nftables.conf
sudo nft -f /etc/nftables.conf
sudo nft list ruleset

A third poller is one command, no reload: sudo nft add element inet filter snmp_pollers '{ 10.99.0.7 }'. Add the address to the file too, or it is gone at the next boot.

Two warnings from experience. Keep a second SSH session open while you load a policy drop chain, because a typo in the SSH line locks you out. And do not use flush ruleset on a host that runs Docker, since it wipes the rules Docker created; put your rules in a table of your own instead. The inet table matches IPv4 and IPv6, but ip saddr only matches IPv4, so add ip6 saddr lines if your pollers use IPv6.

ufw

sudo ufw allow from 10.99.0.5 to any port 161 proto udp
sudo ufw allow from 10.99.0.6 to any port 161 proto udp

# on the trap receiver
sudo ufw allow from 10.20.0.0/16 to any port 162 proto udp

sudo ufw status numbered

ufw denies incoming traffic by default, so these allow rules are all you need. What you should not type is sudo ufw allow 161/udp, which opens the port to every address the host can hear.

firewalld

On the Red Hat family, rich rules give you a source address and a port in one line:

sudo firewall-cmd --get-active-zones

sudo firewall-cmd --permanent --zone=public \
  --add-rich-rule='rule family="ipv4" source address="10.99.0.5/32" port port="161" protocol="udp" accept'
sudo firewall-cmd --permanent --zone=public \
  --add-rich-rule='rule family="ipv4" source address="10.99.0.6/32" port port="161" protocol="udp" accept'

# on the trap receiver
sudo firewall-cmd --permanent --zone=public \
  --add-rich-rule='rule family="ipv4" source address="10.20.0.0/16" port port="162" protocol="udp" accept'

sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --list-rich-rules

Use whichever zone the first command shows for the interface the polls arrive on. firewalld also ships predefined snmp and snmptrap services, but --add-service=snmp on its own opens the port to the whole zone, which is the opposite of what you want.

Network firewalls and router ACLs

On pfSense, OPNsense, a cloud security group or a core router, the rules are the same two, written in that device's terms: UDP 161 from the poller addresses to the device subnets, and UDP 162 from the device subnets to the trap receiver. Put the agent ACL on the device itself as well, so that a mistake in the network firewall does not expose it. The SNMP versions article shows that ACL for net-snmp and Cisco IOS.

Testing the rules

Work from the agent outward.

# On the agent: is anything listening, and on which address?
sudo ss -ulnp | grep -E ':16[12]\b'

# On the poller: does a poll come back?
snmpget -v2c -c n0c-r34d -t 2 -r 1 10.20.4.11 1.3.6.1.2.1.1.3.0

# On either end, while you test
sudo tcpdump -ni any 'udp port 161 or udp port 162'

# From a device or test host: send a coldStart trap to the receiver
snmptrap -v2c -c n0c-tr4p 10.99.0.5 '' 1.3.6.1.6.3.1.1.5.1

The ss line catches the most common problem before any firewall is involved: on Debian and Ubuntu, the packaged snmpd.conf listens on 127.0.0.1 only. Set agentaddress udp:10.20.4.11:161 (the management address, not every address) and restart snmpd. The test trap only shows up if snmptrapd accepts the community; the trap receiver guide covers that file and the v3 trap users.

Read tcpdump from both sides. Requests leaving the poller and never arriving at the agent point at the network firewall. Requests arriving with no reply point at the host firewall, the agent's own ACL or a wrong community. The snmpwalk examples list what each error message means.

Then prove the negative. Run the same snmpget, correct community and all, from a host that is not a poller. It should time out. If it answers, the rule is wider than you meant, and that matters more than any rule that is too tight.

Problems that look like firewall problems

  • Small gets work, big walks time out. Large GETBULK replies are split into IP fragments, and some firewalls drop fragments. Lower -Cr on the poller, or allow fragments on the path.
  • Traps arrive filed under the wrong host. NAT between the device and the receiver replaces the source address. SNMPv1 traps carry the agent address inside the message; v2c and v3 traps do not. Keep NAT off the management path.
  • Traps from a router come from an address you did not expect. A router sends each trap out of whichever interface faces the receiver, so the source may not be the loopback or management address in your ACL. Set the trap source interface (on Cisco IOS, snmp-server trap-source).
  • Everything works from your laptop and nothing works from the poller. The rule names the wrong source. Check the address the poller really sends from with tcpdump.

Before you call it done

Firewall rules are one layer of three. The others are SNMPv3 with authPriv, so that a captured packet gives away nothing, and an ACL on the agent. Never leave UDP 161 open to the internet: an exposed agent answers anyone, and spoofed GETBULK requests turn it into a traffic amplifier aimed at someone else.

  1. UDP 161 open on each agent from the poller addresses only, IPv4 and IPv6.
  2. UDP 162 open on the trap receiver from the device ranges only, matched on destination port.
  3. snmpd bound to the management address, not to everything and not to localhost.
  4. Rules saved so they survive a reboot.
  5. A poll, a walk of a large table and a test trap all confirmed with tcpdump running.

next: SNMP counter to bit rate for the numbers, the Rack list for the gear