MSS
MTU sets a ceiling on the entire IP packet — headers and all. TCP doesn't get to use that whole ceiling for application data, because the IP header and the TCP header both have to fit inside it too. Maximum Segment Size (MSS) is the number that actually answers the question a sending application cares about: how many bytes of real payload can go into one TCP segment without the resulting IP packet exceeding the path's MTU.
The arithmetic behind the number everyone quotes
Take the standard 1500-byte Ethernet MTU. A minimum IPv4 header takes 20 bytes, and a minimum TCP header takes another 20, leaving 1500 - 20 - 20 = 1460 bytes for actual application data. That's where the frequently-cited 1460 figure — visible in the mss 1460 option in the handshake capture from Three-way handshake — actually comes from: it's a direct consequence of the standard MTU minus the two minimum-size headers riding inside it, not an arbitrary round number someone picked.
|<---------------------- 1500-byte Ethernet frame's IP packet -------------------->|
|<-- 20-byte IP header -->|<-- 20-byte TCP header -->|<------ up to 1460 bytes ---->|
| (MSS, application |
| data payload) |
Both header sizes are minimums, not fixed values — either can grow if options are in use (TCP timestamps, window scaling, or IP options), and every extra byte of header comes directly out of the space available for actual data. The 1460 figure assumes the minimum 20-byte headers with no options; a real connection using TCP timestamps, for example, carries a few extra option bytes on every segment, which is one reason the value reported by a live connection sometimes comes in under a clean 1460 — more on reading that number later in this article.
Negotiated once, during the handshake, and never renegotiated
MSS is a TCP option carried in the SYN and SYN-ACK segments — visible directly in the handshake capture from Three-way handshake as mss 1460 and mss 1440. Each side announces the largest segment it is willing to receive, based on its own local interface's MTU, and — critically — each side then uses the other's announced value as the ceiling for what it sends in that direction. A connection's two directions can legitimately use different MSS values if the two hosts' local MTUs differ, since each side is only constraining what's sent toward itself, not dictating a shared value for the whole connection.
This negotiation happens exactly once, at connection setup, and isn't revisited afterward — which is a real limitation. If the path's actual, minimum MTU changes partway through a long-lived connection (a route change moving traffic onto a link with a smaller MTU, for instance), the originally negotiated MSS may end up larger than what the path can now actually carry, and it's Path MTU Discovery, from the previous article, that has to catch and correct that mismatch mid-connection — MSS negotiation itself has no mechanism for noticing this on its own.
Why fitting cleanly into one MSS actually matters
A request or response that fits inside a single MSS-sized segment travels as one self-contained unit: sent once, acknowledged once, with none of the ordering or reassembly considerations that come with spanning multiple segments. A request that runs even slightly over that boundary needs a second segment, and that second segment is subject to everything covered earlier in this module — it has to fit within whatever the current sliding window allows, and depending on network conditions, the two segments aren't guaranteed to be processed by the receiver at exactly the same moment.
This is a genuine, practical reason API designers and protocol implementers pay attention to payload size where it's easy to do so — trimming unnecessary fields from a small, frequent request, for instance, so it reliably fits in one segment rather than spilling into two. It's a marginal optimization for any single request, but on a very high-volume endpoint, consistently avoiding an extra segment (and the header overhead of processing it) per request adds up in aggregate, even though no individual request would ever visibly benefit from the difference.
Reading the negotiated MSS in a live connection
ss reports the effective MSS for an established connection directly, without needing to inspect a handshake capture:
ESTAB 0 0 10.0.5.12:52210 93.184.216.34:443
cubic wscale:7,8 rto:212 rtt:41.2/8.6 mss:1400 cwnd:38 ssthresh:22 bytes_sent:187400 bytes_acked:187400
mss:1400 here is slightly below the theoretical 1460, which is a useful, common real-world signal in its own right: something along this particular path is consuming more header space than the bare minimum — a VPN tunnel adding its own encapsulation headers, or a slightly reduced MTU somewhere on the route, are both common causes. It's not a bug to see a lower value; it's the negotiation correctly reflecting a path with a smaller effective ceiling than plain, untunneled Ethernet.
Clamping MSS: fixing a mismatch without touching MTU
Where MTU's practical scenario left off — a PMTUD black hole caused by ICMP being silently dropped somewhere along a path — the most common actual fix is MSS clamping: a router or firewall rewrites the MSS option in the SYN and SYN-ACK segments passing through it, forcing both sides to negotiate a smaller value than they otherwise would have, specifically so that neither side ever generates a packet large enough to need fragmentation or to hit the broken PMTUD path in the first place. This is especially common at the edge of tunnel interfaces — a VPN gateway, for instance, where the tunnel's own encapsulation overhead reduces the effective MTU below what either endpoint would naturally assume, and clamping the MSS at the tunnel's own boundary means neither endpoint even needs to be aware the tunnel exists.
A representative iptables rule for this on a Linux gateway:
MSS clamping applies to every TCP connection traversing the rule, not just one host or one path
This rule rewrites the MSS option for every SYN and SYN-ACK passing through the FORWARD chain on this device — every connection routed through it is affected, not only the one experiencing a problem. Before applying it on a production gateway, check the existing iptables -t mangle -L -n -v output to confirm there isn't already a conflicting rule, apply it during a maintenance window, and verify with a fresh handshake capture (tcpdump, filtering for the SYN flags as shown in Three-way handshake) that the negotiated MSS afterward is actually the smaller, clamped value you intended, rather than assuming the rule took effect correctly.
--clamp-mss-to-pmtu specifically sets the clamped value based on the outgoing interface's own MTU, which is the right choice for exactly the tunnel-overhead case described above, since it accounts for whatever the actual usable size on that interface is, rather than assuming a fixed, hand-picked number that could be wrong if the interface's MTU ever changes.
Practice exercises
- Given a network using an 1492-byte MTU (a common value on PPPoE-based DSL connections, which add their own encapsulation overhead), calculate the resulting MSS for a connection using minimum-size IP and TCP headers, and explain what would happen to a segment negotiated at the standard 1460 MSS if it were sent across this link without MSS clamping in place.
- Explain why MSS clamping is applied to the SYN and SYN-ACK segments specifically, rather than to the data segments that follow — what would go wrong if a firewall tried to rewrite the MSS option on a segment sent after the handshake had already completed?
- A connection's
ss -tinoutput showsmss:1220, noticeably smaller than the roughly 1400 typical of most connections in the same environment. List at least two plausible, non-broken explanations for that specific value, using this article's discussion of headers and tunnel overhead.
MSS decides how much data fits in a single segment cleanly. The next article covers what happens when data — or an entire IP packet — doesn't fit within the path's MTU at all, and has to be split up mid-transit instead: Fragmentation.
Sources
- IETF, RFC 879 – The TCP Maximum Segment Size and Related Topics
- IETF, RFC 9293 – Transmission Control Protocol (TCP) — defines the MSS option format.