Port 465 vs 587: Hardening Mail Submission the Right Way
Everyone repeats that 465 is deprecated and 587 is the modern submission port. RFC 8314 flipped that in 2018. Here is what actually happens on the wire, why STARTTLS on 587 carries a cleartext window you cannot close, and the exact Postfix/Dovecot config plus openssl and swaks tests to prove your MSA only accepts authenticated, encrypted submission.
EvilMail TeamJuly 24, 202611 min read
An MSA that advertises AUTH before STARTTLS, or accepts plaintext AUTH on 587, is handing user credentials to anyone who can sit on the path — a coffee-shop AP, a compromised router, a hostile transit provider. Passive capture reads the base64 straight off the wire; an active attacker doesn't even need to wait, because the capability list itself is unauthenticated and rewritable before the session is encrypted. This is the failure mode that actually gets people owned, and it has almost nothing to do with which port number you picked.
The port debate is downstream of that, and the conventional wisdom on it is stale. "465 is deprecated, use 587" is a twenty-year-old fact that stopped being true in January 2018, when RFC 8314 ("Cleartext Considered Obsolete") explicitly reclaimed port 465 for Message Submission over implicit TLS and named it the *recommended* option over STARTTLS. If your runbook still calls 465 legacy, your runbook predates the standard you should be following.
The thesis for this piece: expose 465 with implicit TLS as the default, keep 587 only if a middlebox forces you to, and make authenticated, encrypted submission non-negotiable on both — enforced in config, then proven with openssl
465 vs 587 Mail Submission Hardening: Implicit TLS, STARTTLS & Auth Policy — EvilMail Blog
and
swaks
instead of trusted from the docs.
The two ports, precisely — what happens on the wire
The difference is not cosmetic. It is a different sequence of bytes.
Port 465 (implicit / wrapped TLS): TCP connects, and the very first thing that happens is a TLS handshake. EHLO, the capability list, AUTH, MAIL FROM — all of it occurs *inside* the tunnel. There is no plaintext phase. You either complete a valid handshake against a certificate you trust, or you get nothing.
Port 587 (STARTTLS): TCP connects in cleartext. The client sends EHLO in the clear. The server replies in the clear with its capabilities, including 250-STARTTLS. The client issues STARTTLS, *then* the handshake happens, *then* a second EHLO runs inside the tunnel. Everything before that second EHLO — the banner, the first capability list, the STARTTLS negotiation itself — is cleartext and unauthenticated. That is the window an attacker works in.
Port 25 is not submission. It is MTA-to-MTA relay, defined that way since the split formalized in RFC 6409 (Message Submission) versus plain SMTP. End-user clients submitting on 25 is an antipattern — no auth expectation, opportunistic TLS at best, and it's the first port every provider blocks for outbound abuse. If a client is configured for 25, that's a bug, not a fallback.
The naming history is where confusion breeds. Port 465 was originally registered as "SMTPS" in the late 1990s, then deprecated around 1998 in favor of the STARTTLS-on-587 model. That deprecation is the fact everyone memorized. RFC 8314 reversed it deliberately, reassigning 465 as the submission-over-implicit-TLS port precisely because implicit TLS removes the downgrade surface. Don't call 465 legacy. It's the recommendation.
Why STARTTLS on 587 is the weaker default
The attack is STARTTLS stripping, and it works for the dumbest possible reason: the phase it targets is unencrypted and unauthenticated, so it can be edited in flight.
An active MITM sits in the plaintext phase of a 587 session. When the server sends its capability list, the attacker removes or garbles the 250-STARTTLS line. The client, seeing no STARTTLS offered, does one of two things depending on how it's configured: it aborts (good, but many don't), or it silently proceeds with plaintext AUTH (bad, and still common in libraries and misconfigured clients set to "STARTTLS if available"). The credentials cross the wire in the clear. On 465 this attack has no surface — there is no plaintext capability list to edit, because the first byte after TCP is already TLS.
It gets worse than passive stripping. There's a whole class of STARTTLS command-injection bugs — CVE-2011-0411 was the early canonical one, and the 2021 USENIX study *Why TLS is better without STARTTLS* swept up Postfix, Exim, and most of the ecosystem — where bytes the client sends *during* the plaintext phase get buffered and then replayed *after* the handshake completes, inside the trusted session. The plaintext phase isn't just readable; under those bugs it's injectable.
587 has exactly one legitimate reason to exist in 2026: networks where a middlebox, corporate proxy, or upstream policy blocks 465 outright. That's real and it happens. So keep 587 as a fallback — but if you keep it, you harden it so the cleartext window can never carry an AUTH.
Enforcing authenticated submission policy — the part that matters
Neither port is safe if the MSA behind it is sloppy. Three rules are non-negotiable on any submission port:
1.TLS is mandatory before AUTH. The server must refuse AUTH on an unencrypted connection. In Postfix that's smtpd_tls_auth_only=yes.
2.AUTH is mandatory before MAIL FROM. No authenticated session, no envelope. The restriction backbone is permit_sasl_authenticated, reject.
3.Submission ports never relay for unauthenticated senders. This is the open-relay guardrail. An unauthenticated external sender aiming at an external recipient gets 554 5.7.1 Relay access denied, full stop.
Beyond those three, one more rule stops the most common insider-ish abuse: an authenticated user spoofing someone else's From. Bind the login identity to the permitted envelope with smtpd_sender_login_maps plus reject_sender_login_mismatch. Without it, any valid account can send MAIL FROM:<ceo@yourdomain> and your own SPF/DKIM will happily authorize the forgery.
Dovecot is the SASL backend here — on evilmail's stack the password scheme is PLAIN over the private auth socket, mail vhosts under uid/gid 5000 — so Postfix talks to it via smtpd_sasl_type=dovecot and a unix socket rather than running its own SASL. And because submission hardening is also a deliverability concern, wire the abuse posture in the same pass: Postfix anvil and per-user submission rate limits keep one popped account from turning your IP into a spam cannon and torching your reputation.
Config you can paste — Postfix master.cf + main.cf
In master.cf, define both submission services explicitly. The -o overrides matter more than the global settings, because these are what apply on the port an actual user connects to:
/etc/postfix/sender_login maps each envelope address to the SASL login allowed to use it ([email protected][email protected]), then postmap it. TLS 1.2 is the hard floor; 1.3 is preferred and negotiated automatically when both ends support it.
The DNS layer completes the picture. Submission hardening protects the pipe; these records tell *senders to you* to demand encryption and tell the world *who may send as you*:
The MTA-STS policy file lives at https://mta-sts.evilmail.pro/.well-known/mta-sts.txt with version, mode, mx, and max_age (start mode: testing, then move to mode: enforce with max_age: 604800). The TLSA/DANE record only works if the zone is DNSSEC-signed, and note that clients checking DANE for submission ports is far less common than for MX-to-MX on 25 — treat it as belt-and-suspenders, not a substitute for MTA-STS. SPF, DKIM, and DMARC sit alongside as the *authorization* triad — the "who may send" layer that your authenticated-submission enforcement feeds into, distinct from the "is the pipe encrypted" question this article is about.
Prove it — openssl and swaks
Never trust the config file. Test the running socket.
bash
# 465: expect an IMMEDIATE handshake, cert chain, Protocol: TLSv1.3
openssl s_client -connect mail.evilmail.pro:465
# 587: expect a plaintext 220 banner, THEN the upgrade
openssl s_client -starttls smtp -connect mail.evilmail.pro:587
Now the negative test that most people skip. Connect to 587 in plaintext and try to authenticate *before* STARTTLS:
text
$ nc mail.evilmail.pro 587
220 mail.evilmail.pro ESMTP Postfix
EHLO test
250-mail.evilmail.pro
250-STARTTLS
...
AUTH LOGIN
530 5.7.0 Must issue a STARTTLS command first
That 530 is the whole point — the server refuses to take credentials in the clear. If you get a 334 prompt instead, smtpd_tls_auth_only isn't taking effect and you have a live credential-leak bug.
Send real authenticated test mail over each port with swaks:
TLS 1.0/1.1 must appear nowhere in that output. If they do, your smtpd_tls_protocols isn't being honored on that service.
Migration and client reality
Set autoconfig/autodiscover to advertise 465 SSL as primary, 587 STARTTLS as secondary. Thunderbird, Apple Mail, iOS Mail, and effectively every modern library have supported implicit-TLS submission for years — the "465 breaks clients" fear is a decade past its expiry. The real friction is hardcoded 587 in server-side app SMTP: PHPMailer wants SMTPSecure = 'ssl' with Port = 465; Nodemailer wants secure: true with port: 465. Both are one-line changes. Keep 587 running through the transition, watch postfix/submission logs for who's still on it, then decide whether to retire it.
Hardening checklist
Expose 465 implicit TLS as the default submission port; make it primary in autoconfig.
Keep 587 only as a fallback, and only with smtpd_tls_auth_only=yes enforced.
TLS 1.2 floor, 1.3 preferred; SSLv3/TLS1.0/TLS1.1 fully disabled on both ports.
AUTH only after TLS — verify with the plaintext AUTH negative test returning 530.
`permit_sasl_authenticated, reject` on every restriction class of every submission service.
`smtpd_sender_login_maps` + `reject_sender_login_mismatch` so authed users can't spoof another From.
Block port 25 for end-user auth — submission never happens there.
Publish MTA-STS (enforce) + TLS-RPT; add DANE/TLSA if the zone is DNSSEC-signed.
Rate-limit per authenticated user (anvil) to contain a popped account.
Re-run the `openssl` + `swaks` + open-relay tests after every change — the config file is a claim, the socket is the truth.