Symptoms
You commit a firewall filter to your Juniper router or switch, convinced it's going to block specific traffic — and then you pull up a packet capture or run a quick ping and watch the traffic sail right through. The filter is there in the configuration, committed without errors, and yet nothing gets blocked. This is one of those maddening situations that makes you question everything you think you know about Junos firewall filters.
Common signs that your filter isn't doing what you expect include:
- Traffic that should be dropped continues to reach the destination
show firewall log
shows zero hits on the blocking term- A ping or telnet to an address that should be blocked succeeds
- Filter counters remain at zero even after sustained traffic flows
- No syslog messages appear even with logging configured on the term
Before you tear the whole filter apart and start over, walk through the root causes below. In my experience it's almost always one of five or six specific mistakes — and most are quick to fix once you know what you're looking for.
Root Cause 1: Filter Applied in the Wrong Direction
Why It Happens
This is the single most common mistake I see, and it catches experienced engineers just as often as newcomers. Junos firewall filters are strictly directional — you apply them as either input (traffic arriving on the interface) or output (traffic leaving through the interface). Getting the direction wrong means your filter is evaluated against a completely different traffic stream, so the packets you want to block never even encounter it.
The confusion usually comes from thinking about traffic flow from the perspective of the end host rather than the router interface. If external hosts are sending traffic in through your WAN-facing interface, that's an input filter on that interface. If you're trying to block traffic leaving toward a particular server, that could be an output filter on the server-facing interface — or an input filter somewhere upstream depending on the topology. Getting this wrong is easy when you're moving fast.
How to Identify It
Dump the interface detail and look for where the filter is applied:
infrarunbook-admin@sw-infrarunbook-01> show interfaces ge-0/0/1 detail | match filter
Input Filter: none
Output Filter: block-external
If external traffic enters on ge-0/0/1 and you've applied the filter as output, it will never match ingressing packets. Here's the misconfigured state in the configuration tree:
interfaces {
ge-0/0/1 {
unit 0 {
family inet {
filter {
output block-external; /* WRONG: traffic enters on this interface */
}
}
}
}
}
How to Fix It
Remove the incorrect application and add it in the right direction:
infrarunbook-admin@sw-infrarunbook-01# delete interfaces ge-0/0/1 unit 0 family inet filter output block-external
infrarunbook-admin@sw-infrarunbook-01# set interfaces ge-0/0/1 unit 0 family inet filter input block-external
infrarunbook-admin@sw-infrarunbook-01# commit
infrarunbook-admin@sw-infrarunbook-01> show interfaces ge-0/0/1 detail | match filter
Input Filter: block-external
Output Filter: none
After the commit, immediately generate test traffic and watch
show firewall login real time to confirm hits are landing on the expected term. If you're unsure which direction to use, trace the packet path first with
show route <destination>to understand egress interfaces, then reason backward.
Root Cause 2: Term Order Is Wrong
Why It Happens
Junos evaluates firewall filter terms sequentially, top to bottom, and stops at the first match. This is fundamental and non-negotiable. If a broad permissive term appears earlier in the filter than your blocking term, the permissive term wins every time and your block term is never reached.
I've watched this happen repeatedly when someone adds a "block specific host" term to an existing filter that already has a catch-all permit for a large address block. The permit matches first because it's higher in the term list, and the block term just sits there doing nothing.
How to Identify It
Dump the full filter and read the terms in order:
infrarunbook-admin@sw-infrarunbook-01> show firewall filter block-external
Filter: block-external
Term: permit-management
from:
source-address: 10.0.0.0/8
then:
accept;
Term: block-rogue-host
from:
source-address: 10.4.88.15/32
then:
discard;
Term: default-permit
then:
accept;
The problem is obvious once you see it laid out:
permit-managementmatches all of 10.0.0.0/8 with an accept. Since 10.4.88.15 falls within that /8, every packet from that host is accepted at the first term. The
block-rogue-hostterm never fires.
How to Fix It
Reorder the terms so more specific matches appear before broader ones. Junos provides the
insertcommand for exactly this:
infrarunbook-admin@sw-infrarunbook-01# edit firewall filter block-external
infrarunbook-admin@sw-infrarunbook-01# insert term block-rogue-host before term permit-management
infrarunbook-admin@sw-infrarunbook-01# commit
infrarunbook-admin@sw-infrarunbook-01> show firewall filter block-external
Filter: block-external
Term: block-rogue-host
from:
source-address: 10.4.88.15/32
then:
discard;
Term: permit-management
from:
source-address: 10.0.0.0/8
then:
accept;
Term: default-permit
then:
accept;
Now 10.4.88.15 hits the discard term before the broader permit-management term can accept it. As a general rule: always order terms from most specific to least specific when addresses or prefixes overlap.
Root Cause 3: Match Condition Is Too Specific
Why It Happens
Every condition inside a Junos filter term's
fromblock is ANDed together — all conditions must be simultaneously true for the term to match. If even one condition doesn't match the actual packet, the entire term is skipped, regardless of how well everything else aligns. This catches people who write multi-condition terms and then encounter traffic that satisfies most but not all of the conditions.
The most common offender I see is combining protocol, destination-port, and an address prefix, then being confused when only some traffic gets blocked. Writing a term that matches TCP port 53 to a specific host will completely ignore UDP port 53 to that same host — and DNS is predominantly UDP.
How to Identify It
Consider this term:
term block-dns {
from {
destination-address 10.99.1.10/32;
protocol tcp;
destination-port 53;
}
then discard;
}
Generate traffic to that destination using both TCP and UDP, then check the firewall log:
infrarunbook-admin@sw-infrarunbook-01> show firewall log
Log :
Time Filter Action Interface Protocol Src Addr Dest Addr
17:42:03 block-external D ge-0/0/1 TCP 10.4.22.1 10.99.1.10
17:42:05 block-external - ge-0/0/1 UDP 10.4.22.1 10.99.1.10
The D action means discard — TCP is being dropped correctly. The - means no action was logged, indicating the UDP packet didn't match any term and fell through to the implicit default accept. That's your problem. You can also confirm with a packet capture on the egress interface showing UDP packets reaching the destination.
How to Fix It
Either add a parallel term for UDP, or if the intent is to block all traffic to that host, drop the protocol and port conditions entirely:
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet filter block-external term block-dns-udp from destination-address 10.99.1.10/32
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet filter block-external term block-dns-udp from protocol udp
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet filter block-external term block-dns-udp from destination-port 53
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet filter block-external term block-dns-udp then discard
infrarunbook-admin@sw-infrarunbook-01# insert term block-dns-udp before term block-dns
infrarunbook-admin@sw-infrarunbook-01# commit
If the intent was always to block everything to that host regardless of protocol or port, simplify the match to just the destination address and remove the protocol and port conditions. Simpler terms with fewer conditions are easier to reason about and far easier to debug later.
Root Cause 4: Action Not Set in the Matching Term
Why It Happens
This one is subtle. In Junos, if a filter term has a
fromclause but no
thenclause, the default implicit action is accept. You wrote the term, defined the match conditions, and forgot the actual action — so Junos accepts the packet and moves on, never discarding anything.
This typically happens when someone writes the
fromblock first to test whether the match logic is correct, commits it, forgets to come back and add the
then discard, and then closes the session. It also happens when copying configuration snippets where the action line got stripped out during editing.
How to Identify It
Use the
display setoption to see what was explicitly configured versus what Junos infers:
infrarunbook-admin@sw-infrarunbook-01> show configuration firewall | display set
set firewall family inet filter block-external term block-rogue-host from source-address 10.4.88.15/32
set firewall family inet filter block-external term default-permit then accept
Notice that
block-rogue-hosthas a
fromcondition but no
thenline at all. Now compare with the operational view:
infrarunbook-admin@sw-infrarunbook-01> show firewall filter block-external
Filter: block-external
Term: block-rogue-host
from:
source-address: 10.4.88.15/32
then:
accept; <-- implicit default, never explicitly configured
Term: default-permit
then:
accept;
Junos shows the implicit accept in the operational output. It won't appear in
show configurationor
display setbecause it was never configured — which makes it easy to miss on a config review. The firewall log also won't show action entries for these packets since there's nothing to log against.
How to Fix It
Add the intended action explicitly. Use
discardto silently drop, or
rejectto drop and return an ICMP unreachable to the source. For security filtering,
discardis generally preferred since it gives no feedback to a potential attacker:
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet filter block-external term block-rogue-host then discard
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet filter block-external term block-rogue-host then count rogue-host-drops
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet filter block-external term block-rogue-host then log
infrarunbook-admin@sw-infrarunbook-01# commit
Adding
countand
logalongside
discardgives you visibility — the counter increments on every matched packet, and hits appear in
show firewall log, which makes verifying the fix immediate and unambiguous.
Root Cause 5: Filter Not Applied to Any Interface
Why It Happens
A firewall filter sitting unattached in the configuration does exactly nothing. You could have the most perfectly constructed filter with flawless term order, precise match conditions, and explicit discard actions — and if it's not applied to an interface, traffic flows through the router with no filtering whatsoever.
This is embarrassingly easy to do. You create the filter, review the configuration, run
commit check, everything looks clean, and you close the session. Applying the filter to the interface was the next step and it simply didn't happen. I've done this myself after a long change window.
How to Identify It
Search for the filter name anywhere in the interface configuration:
infrarunbook-admin@sw-infrarunbook-01> show configuration | match block-external
filter block-external;
term block-rogue-host {
term block-dns {
If those matches only appear under the
firewall { ... }stanza and nowhere under
interfaces { ... }, the filter isn't applied anywhere. Cross-check a specific interface directly:
infrarunbook-admin@sw-infrarunbook-01> show interfaces ge-0/0/1 detail | match filter
[no output]
No output means no filter applied. A correctly configured interface would show:
infrarunbook-admin@sw-infrarunbook-01> show interfaces ge-0/0/1 detail | match filter
Input Filter: block-external
How to Fix It
Determine which interface the relevant traffic transits and apply the filter in the correct direction. Use
show routeto find the egress path for traffic you want to intercept, then apply accordingly:
infrarunbook-admin@sw-infrarunbook-01> show route 10.4.88.15
inet.0: 14 destinations, 14 routes (14 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both
10.4.88.0/24 *[Static/5] 00:12:34
> to 10.0.0.1 via ge-0/0/1
infrarunbook-admin@sw-infrarunbook-01# set interfaces ge-0/0/1 unit 0 family inet filter input block-external
infrarunbook-admin@sw-infrarunbook-01# commit
infrarunbook-admin@sw-infrarunbook-01> show interfaces ge-0/0/1 detail | match filter
Input Filter: block-external
After applying, verify with
show firewall logwhile generating test traffic. You should see hits immediately if the term match conditions are correct and the direction is right.
Root Cause 6: Wrong Address Family
Why It Happens
Junos firewall filters are address-family specific. A filter defined under
family inetonly evaluates IPv4 packets. If the traffic you're trying to block is IPv6, you need a separate filter under
family inet6. The families don't share filters, and there's no cross-family inheritance.
This becomes a real problem when a network has dual-stack interfaces and an engineer creates a blocking filter only in the IPv4 family. IPv6 traffic from the same source walks straight through, and it can look like the filter isn't working when really it just doesn't apply to that protocol family.
How to Identify It
infrarunbook-admin@sw-infrarunbook-01> show configuration firewall
family inet {
filter block-external {
term block-rogue-host {
from {
source-address 10.4.88.15/32;
}
then discard;
}
term default-permit {
then accept;
}
}
}
/* No family inet6 section -- IPv6 traffic is unfiltered */
How to Fix It
Create a parallel filter under
family inet6for the equivalent IPv6 addresses and apply it to the same interface:
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet6 filter block-external-v6 term block-rogue-host from source-address 2001:db8:cafe::abcd/128
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet6 filter block-external-v6 term block-rogue-host then discard
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet6 filter block-external-v6 term default-permit then accept
infrarunbook-admin@sw-infrarunbook-01# set interfaces ge-0/0/1 unit 0 family inet6 filter input block-external-v6
infrarunbook-admin@sw-infrarunbook-01# commit
After committing, verify both families are covered on the interface:
infrarunbook-admin@sw-infrarunbook-01> show interfaces ge-0/0/1 detail | match filter
Input Filter: block-external
Input6 Filter: block-external-v6
Root Cause 7: Implicit Default Accept at End of Filter
Why It Happens
Every Junos firewall filter has an implicit default accept at the very end if you don't define an explicit catch-all term. This is by design — Junos defaults to permitting unmatched traffic unless you explicitly say otherwise. If your filter only contains specific block terms and no explicit default, any traffic that doesn't match your block terms gets accepted silently with no log entry and no counter hit.
This isn't always a problem — sometimes you want most traffic to pass and only block a handful of specific flows. But when the implicit default causes unexpected behavior, it's hard to spot because it leaves no trace in logs or counters.
How to Identify It
Check whether your filter has an explicit catch-all term at the end:
infrarunbook-admin@sw-infrarunbook-01> show firewall filter block-external
Filter: block-external
Term: block-rogue-host
from:
source-address: 10.4.88.15/32
then:
discard;
/* No explicit default term -- unmatched traffic accepted implicitly */
If the last term in the filter is a blocking term with no subsequent catch-all, all non-matching traffic is accepted without any visibility into what's passing through.
How to Fix It
Always add an explicit default term as the last entry in the filter. This documents the intent, makes the behavior visible through counters, and removes any ambiguity:
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet filter block-external term default-accept then accept
infrarunbook-admin@sw-infrarunbook-01# set firewall family inet filter block-external term default-accept then count all-accepted-traffic
infrarunbook-admin@sw-infrarunbook-01# commit
infrarunbook-admin@sw-infrarunbook-01> show firewall filter block-external
Filter: block-external
Term: block-rogue-host
from:
source-address: 10.4.88.15/32
then:
count rogue-host-drops;
log;
discard;
Term: default-accept
then:
count all-accepted-traffic;
accept;
With explicit counters on both terms, you can tell at a glance exactly how much traffic is hitting each path. If
rogue-host-dropsis stuck at zero and
all-accepted-trafficis climbing, you know the blocking term isn't matching — which sends you back to diagnose the match conditions.
Prevention
Most of these mistakes are preventable with a consistent commit workflow. Before declaring any new firewall filter done, run this validation sequence explicitly — don't skip it even when you're confident:
infrarunbook-admin@sw-infrarunbook-01> show firewall filter block-external
infrarunbook-admin@sw-infrarunbook-01> show interfaces ge-0/0/1 detail | match filter
infrarunbook-admin@sw-infrarunbook-01> show firewall log
infrarunbook-admin@sw-infrarunbook-01> show firewall filter block-external counter
Put counters in every term, including accept terms. Counters cost nothing at scale and make troubleshooting dramatically faster. When you can see exactly how many packets hit each term, the diagnosis for every root cause above becomes a matter of reading a number rather than reasoning from indirect evidence.
For critical filters, generate controlled test traffic before relying on the filter in production. Use a known source address, send traffic to a known destination, and watch the counters and logs in real time. This takes two minutes and eliminates any doubt about whether the filter is doing what you think.
Document the intent of each filter in commit messages. When someone — including you, six months from now — reviews the configuration and wonders why a particular term exists, a commit message like "block 10.4.88.15 per security team request SR-4421 — host identified as compromised" is infinitely more useful than the filter definition alone.
Finally, when building filters that need to handle both IPv4 and IPv6 traffic, make it a rule to always create both
family inetand
family inet6versions in the same change. Doing them together removes the risk of forgetting to come back and finish the IPv6 side later.
Juniper firewall filters are deterministic and reliable once you understand the evaluation model. The behavior that looks like a bug is almost always a configuration mistake — and with the right diagnostic workflow, you can identify and fix every one of the root causes above in minutes rather than hours.
