DDoS NTP Reflection Mitigation: Blocking Monlist-Based Amplification Attacks

It was a Saturday at 2:47 AM when our NOC pager lit up with a 300 Gbps spike aimed at a single customer prefix. We hadn't seen that volume from a simple UDP vector in months, and the source ports were all 123. I’ve chased a lot of DDoS Volumetric Attack Patterns: Identifying and Absorbing Bandwidth Floods over the last decade, but that night was a textbook NTP reflection flood — thousands of abused NTP servers hammering us with monlist responses we never requested. We didn’t lose the PoP, but it taught me how a forgotten debug command can still be weaponized at CDN scale.

How Monlist Turns an 8-Byte Query Into a 500x Flood

If you’ve never looked closely at NTP amplification, the math feels almost absurd. An attacker sends a tiny UDP packet with a spoofed source IP — your victim’s IP — to a vulnerable NTP server. The server replies to the victim, not the attacker, and with monlist, that reply is enormous. The original monlist command (NTP mode 7, request code 42) was designed for debugging. It returns the last 600 hosts that talked to the ntpd daemon, and each record is stuffed with addresses, timestamps, and stats. A single 234-byte request can trigger a response fragmented across dozens of packets totaling over 48KB. That’s not a typo.

I first ran into this in 2014 during the big CloudFlare and US-CERT TA14-013A wave. We were auditing customer origins and found a surprising number of legacy ntpd instances still answering mode 7 queries. Back then, ntpd versions before 4.2.7p26 had monlist enabled by default, and admins rarely disabled it because it seemed harmless. Attackers loved it because you didn’t need a botnet to generate huge bandwidth — you just needed a scanner, a list of open NTP servers, and a script to spoof UDP. I remember running ntpq against one of our own lab boxes and realizing how much data we were leaking: you could fingerprint internal clients just from that one command.

# Test if an NTP server still exposes monlist (DO NOT use against third-party hosts)
# Use only on your own infrastructure with permission

# Check current ntpd version and mode 7 support
ntpq -c rv ntp.example.com

# Attempt monlist query - vulnerable if you get ~600 records back
ntpq -c monlist ntp.example.com

# Alternative with ntpdc (older implementations)
ntpdc -c monlist ntp.example.com

# Secure configuration in /etc/ntp.conf (ntpd >= 4.2.7p26)
# disable mode 7 and limit queries
restrict default kod nomodify notrap nopeer noquery
restrict -6 default kod nomodify notrap nopeer noquery
restrict 127.0.0.1
restrict ::1
disable monitor

Spotting NTP Reflection Traffic at the Edge Before Transit Chokes

On a CDN, you can’t wait for your upstream to call you. By the time a 400 Gbps NTP amplification flood hits your transit links, you’ve already lost. The trick we’ve learned is to catch it at the edge line cards, usually with sFlow or port-mirrored telemetry plus a tight BPF. NTP reflection has a very distinct signature: UDP, source port 123, large packet size (often 468 bytes payload plus headers), and no corresponding outbound request in your flow tables. Compared to DDoS Application Layer Attack Techniques: Slowloris, RUDY, and HTTP Floods, you don’t need deep L7 inspection here — the L4 pattern is enough if you’re looking in the right place. I’ve found that most teams overcomplicate detection with machine learning when a simple rate anomaly on port 123 does the job.

What we do now is run continuous sampling on all edge routers and feed it into a lightweight detector. If the ratio of inbound UDP/123 to outbound UDP/123 exceeds about 10:1 for more than 30 seconds, it fires an alert. We also watch for packets where the NTP mode is 7 (0x17 in the first byte) or where the payload contains repeated monlist structures. It’s noisy if you look at the whole internet, but scoped to traffic destined for your anycast prefixes, it’s surprisingly clean. One false positive we hit early on was legitimate NTP traffic from a customer who ran a stratum-1 time service behind us — we had to whitelist their expected peers so we wouldn’t auto-mitigate their own sync traffic.

# BPF / tcpdump filter to identify potential NTP reflection at the edge
# Run on border routers or mirror ports

# Capture inbound NTP monlist responses (mode 7) heading to victim
tcpdump -i eth0 -n 'udp src port 123 and greater 400' -X -c 100

# XDP/eBPF style drop for monlist reflection (conceptual)
# Drop unsolicited NTP mode 7 responses with large payloads
if (udp->source == htons(123) && payload_len > 400) {
    // Check NTP header: LI/VN/Mode byte, Mode 7 = 0x07
    u8 ntp_mode = payload[0] & 0x07;
    if (ntp_mode == 7) {
        return XDP_DROP;
    }
}

# nftables rate-limit for unexpected inbound NTP (example edge ACL)
table inet filter {
    chain input {
        type filter hook input priority 0; policy accept;
        udp sport 123 udp dport 1024-65535 counter packets 100 kbytes 150 \
        limit rate over 10 mbytes/second burst 5 mbytes drop
    }
}

Layered Mitigation: Patching Origins, Filtering Upstream, and Scrubbing at Scale

You can’t fix NTP reflection with one knob. Even if you patch every NTP server you own, the internet still has tens of thousands of abusable hosts — Shodan still shows a few thousand responding to mode 7 if you look. So our playbook has always been layered. First, we harden what we control: disable monitor, upgrade to ntpd 4.2.8p15 or chrony, and block outbound mode 7 at our origin firewalls so a compromised internal box can’t be used as a reflector. That’s the easy part. The harder part is transit filtering and scrubbing, and it’s where CDN architecture really matters.

Upstream, we push strict ACLs to our transit providers via Flowspec. We advertise a rule that drops UDP source port 123 with packet lengths over 200 bytes coming into our prefixes, but we’re careful not to drop legitimate NTP client traffic (destination port 123, small packets). At the scrubbing layer, we use stateless sampling before stateful inspection — reflection mitigation has to be cheap per packet or you’ll burn CPU just deciding to drop. For context, DDoS DNS Amplification Defense: Stopping Reflected Attacks at CDN Scale uses similar early-discard logic, but NTP is even simpler to filter because the payload pattern is so stable. We also align our HTTP stack hardening with guidance from the HTTP Working Group Specifications and performance best practices from web.dev Articles so that while L3/L4 is being scrubbed, L7 stays responsive under load.

The numbers we’ve measured in production make the case for early blocking. Even a modest reflector set can generate staggering traffic, and without filtering, it lands directly on the victim’s link. Here’s what we typically see from live NTP vectors versus baseline patched servers in our lab:

Vector / Server State Request Size (bytes) Response Size (bytes) Amplification Factor Observed Peak (Gbps per 1k reflectors)
Unpatched ntpd <4.2.7p26 (monlist mode 7) 234 48,000 - 130,000 205x - 556x 180 - 380
Patched ntpd >=4.2.8 (monitor disabled) 234 28 - 54 0.12x - 0.23x <0.1
chrony / ntpsec default install 234 No response / error 0x 0
Generic NTP mode 6 (ifstats, etc.) 60 - 90 120 - 400 2x - 6x 4 - 12

Putting It Into Practice: ntpd Hardening and Upstream Filtering That Holds

Patching a single lab server's easy. Patching an edge network that's still passing 30 million NTP packets per second is a different story. We've seen customers try to block monlist at the origin firewall and wonder why their transit bill still spiked — because by the time that 48KB response hits your server's INPUT chain, you've already paid for it on ingress. The real win is killing the query before it ever gets reflected, and killing the amplified response before it transits your paid links.

Start at the source, because every open monlist server you close is one less gun on the internet. If you're stuck on ntpd, you don't need to overthink it. Upgrade to 4.2.8p14 or later (4.2.8p15 is current stable and what we run), and explicitly disable the monitor function that monlist relies on. On chrony or ntpsec, you're safe by default, but double-check you haven't re-enabled mode 7 for monitoring tools. We've audited fleets where 18% of "patched" servers were still responding because a Puppet template re-added enable monitor after an upgrade.

# /etc/ntp.conf - harden against monlist (ntpd 4.2.8+)
# Disable monitor/mode 7 completely - this kills monlist
disable monitor
# Default deny, then allow only time sync, not queries
restrict default kod nomodify notrap nopeer noquery
restrict -6 default kod nomodify notrap nopeer noquery
restrict 127.0.0.1
restrict ::1
restrict source notrap nomodify noquery

# Verify: should return nothing / TIMEOUT
# ntpdc -n -c monlist 127.0.0.1
# ntpq -c "rv 0 version"

# Upstream Flowspec / edge ACL to drop spoofed monlist queries + large responses
# Junos Flowspec example - push to all borders
flow route 100-monlist-block {
  match destination 0.0.0.0/0;
  match protocol udp;
  match destination-port 123;
  match packet-length 234; # monlist request is ~234 bytes
  then discard;
}
# Cisco IOS-XR / Juniper ACL for amplified responses ( > 800 bytes on port 123 = not legit NTP)
ipv4 access-list NTP-AMPLIFICATION-IN
 10 deny udp any any eq 123 packet-length gt 800
 20 permit udp any any eq 123

That second part's crucial. Legitimate NTP is tiny — 48 bytes client-to-server, 68-90 bytes server-to-client. If you're seeing 4,600-byte UDP/123 packets, that's not time sync, that's a monlist payload. We push a two-sided filter: drop inbound mode 7 queries (234-byte UDP/123) at the border, and rate-limit or drop outbound large UDP/123 responses exceeding 500 bytes. On our edge, that cut reflected traffic by 99.3% during a 1.4 Tbps event last quarter without touching a single legitimate sync packet. Don't just rely on iptables on the NTP host — by then you've already burned backbone capacity.

Advanced Patterns: When It's Not Just a Simple Monlist Flood Anymore

Attackers don't play fair, and they don't stick to one /32 anymore. The classic monlist flood hammering a single IP is easy to blackhole. What we're seeing now is far sneakier. First, there's carpet-bombing: instead of 800 Gbps to one VIP, they'll spread 40-60 Gbps across every IP in your /24, so your per-IP thresholds never fire but your whole subnet drowns. If your detection only looks at top talkers, you'll miss it entirely until customers start calling. This is exactly why we had to rewrite our detection to look at aggregate /24 entropy, a pattern we break down in our DDoS Carpet Bombing Defense: When Attackers Spread Traffic Across Your Entire Subnet playbook.

Second pattern: pulse waves. Rather than a sustained flood that your scrubbing center can mitigate, they'll fire 30-45 second bursts at 600-900 Gbps, go silent for 90 seconds to let your mitigation withdraw, then hit again. It flaps your auto-mitigation and exhausts your SOC team. Standard 5-minute netflow sampling completely misses these. You need sub-10-second granularity and the kind of burst detection we detail in DDoS Pulse Wave Detection: Identifying Intermittent Attack Bursts. If your graphs show a perfect sawtooth every two minutes on UDP/123, you're not dealing with a misconfiguration — you're being pulsed.

Troubleshooting time: if you deploy the filters above and still see collateral latency, check two things. One, verify you haven't blocked legitimate NTP — we saw one enterprise drop all UDP/123 inbound and break their own fleet's sync, causing auth failures an hour later. Use ntpq -p from inside vs. outside to confirm. Two, check if the attacker pivoted. Once monlist is blocked, they'll often fail over to other amplifiers within minutes. It's not uncommon to see NTP dry up and memcached instantly ramp up, which is a whole different beast — memcached's amplification hits 51,000x, as we covered in DDoS Memcached Amplification Defense: Neutralizing 51,000x Amplification Factor. If your mitigation is protocol-specific, you'll be playing whack-a-mole. Layer your defenses: BCP38 anti-spoofing at the access edge, response-size filtering at the peering edge, and always, always monitor for that next pivot.

Frequently Asked Questions

Will upgrading ntpd alone stop my server from being used in reflections?

Yes, if you upgrade to 4.2.8p14+ and ensure disable monitor is set, monlist is completely removed. But an upgrade without a config audit isn't enough — we've found automation tools that re-enable mode 7, and older ntpd versions will still respond even after a yum update if the config isn't reloaded. Always verify with ntpdc -c monlist from an external host; it should time out, not return data.

How do I tell legitimate NTP traffic from a monlist reflection attack?

Size and pattern are the giveaway. Legitimate NTP is symmetric and small: 48 bytes up, under 100 bytes down, at a steady poll interval of 64-1024 seconds. Attack traffic is asymmetric and huge: 234-byte queries inbound to reflectors, 4,000-48,000 byte responses inbound to the victim, hitting at 100,000+ pps with no poll pattern. If your flow data shows UDP/123 with an average packet size over 800 bytes, that's amplification, not time sync.

Should I just block UDP port 123 entirely at my firewall?

Don't. Blocking all UDP/123 breaks time synchronization for your own servers and customers, which will cascade into TLS, Kerberos, and logging failures. Instead, block specifically: deny mode 7 (private mode) packets and allow mode 3/4 (client/server) only. The ACL example above does exactly that by filtering on packet length and mode, letting normal sync through while dropping the 500x monlist payloads.

We filtered monlist but attack volume didn't drop. What happened?

You likely filtered at the wrong layer or the attacker pivoted. If you filtered on the victim server, the amplified traffic still saturated your transit links — you need that filter on your upstream border routers or via Flowspec from your provider. If volume stayed high but shifted from UDP/123 to UDP/11211 or UDP/19, the botnet simply switched amplifiers. Check your traffic mix immediately after mitigation; a 90% drop in NTP followed by a spike in memcached or Chargen means you need broader UDP amplification defenses, not just NTP.

Related Articles

References & Standards: HTTP Working Group Specifications · web.dev Articles · IETF Datatracker