Hardening Postfix TLS to Pass Internet.nl in 2026
On April 21, 2026, Internet.nl rolled out a fully updated TLS test based on the latest NCSC-NL guidelines. Mail servers that previously passed the test may now show new warnings. This guide walks through the exact fixes on Debian 13 Trixie with Postfix 3.9 or later, based on hands-on troubleshooting on a production server.
The three issues internet.nl now flags are:
- CBC-based cipher suites, marked as insufficient
- Camellia and ARIA cipher suites, marked as phase-out
- SHA1 as a TLS 1.2 key exchange hash function, marked as insufficient
Each has a specific fix. Let’s go through them one by one.
Before you start: the internet.nl rate limit issue
Internet.nl sends a large number of rapid connections to your server during its test suite, checking STARTTLS, DANE, cipher suites, signature algorithms, and more. If your Postfix server has a connection rate limit configured, internet.nl will hit it before the test completes.
Check your current rate limit settings:
postconf smtpd_client_connection_rate_limit
postconf smtpd_client_event_limit_exceptionssmtpd_client_event_limit_exceptions = $mynetworks, 62.204.66.10, 2a00:d00:ff:162:62:204:66:10This keeps the rate limit in place for everyone else while allowing internet.nl to complete its test. After editing, run:
postfix reloadAlso check whether CrowdSec has banned the internet.nl IP from a previous incomplete test run:
cscli decisions list | grep -E "62.204.66|2a00:d00"This searches for the known internet.nl IP ranges rather than specific addresses. If internet.nl changes their test IPs in the future, a range-based search is more likely to catch the new addresses in your decisions list.
If a decision exists, delete it before re-running the test:
cscli decisions delete --ip 62.204.66.10
cscli decisions delete --ip 2a00:d00:ff:162:62:204:66:10Adding internet.nl to the CrowdSec whitelist
CrowdSec may ban internet.nl’s IP addresses during a test run if the rapid connections trigger one of its scenarios. Add them permanently to the whitelist in /etc/crowdsec/parsers/s02-enrich/whitelists.yaml:
whitelist:
reason: "private ipv4/ipv6 ip/ranges"
ip:
- "::1"
- "62.204.66.10"
- "2a00:d00:ff:162:62:204:66:10"
cidr:
- "127.0.0.0/8"
- "192.168.0.0/16"
- "10.0.0.0/8"
- "172.16.0.0/12"Then reload CrowdSec:
systemctl reload crowdsecAt the time of writing (May 2026), internet.nl uses 62.204.66.10 (IPv4) and 2a00:d00:ff:162:62:204:66:10 (IPv6) for its test probes. The IPv6 address appears only during the initial port probe at the start of the test. If internet.nl fails to complete in the future, check your Postfix logs for the current source addresses and update this whitelist accordingly.
Fix 1: cipher suites and TLS hardening
Rather than using a blacklist approach with smtpd_tls_exclude_ciphers, the cleaner and more explicit solution is to define exactly which ciphers Postfix should offer using tls_high_cipherlist. This is a whitelist approach: instead of subtracting weak ciphers from OpenSSL’s broad “high” set, you define precisely what “high” means.
Add these directives to /etc/postfix/main.cf:
smtpd_tls_ciphers = high
smtpd_tls_mandatory_ciphers = high
tls_high_cipherlist = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256This explicitly limits TLS to AES-GCM and CHACHA20-POLY1305 cipher suites only, all with ECDHE for forward secrecy. CBC, Camellia, ARIA and all other weak cipher families are excluded by definition since they are simply not in the list. No smtpd_tls_exclude_ciphers is needed.
Your protocol restrictions should already exclude TLS 1.0 and 1.1:
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1Also add these global TLS hardening directives:
tls_preempt_cipherlist = yes
tls_ssl_options = NO_COMPRESSIONtls_preempt_cipherlist = yes tells Postfix to prefer its own cipher order over the client’s, ensuring stronger ciphers are always negotiated first. tls_ssl_options = NO_COMPRESSION prevents CRIME attack exploitation by disabling TLS compression. NO_RENEGOTIATION disables TLS renegotiation, preventing a class of renegotiation-based attacks. All three are global settings applying to both inbound and outbound TLS.
Reload Postfix and verify:
postfix reload
postconf tls_high_cipherlist
postconf tls_ssl_optionsFix 2: SHA1 signature algorithms for TLS 1.2
This is the more complex fix. Internet.nl tests whether your server offers SHA1 as a signature algorithm during the TLS 1.2 handshake. This is entirely separate from cipher suite configuration. The test explanation says it explicitly: “The supported hash functions can be configured via a separate TLS setting (e.g. SignatureAlgorithms in OpenSSL) and are not part of the cipher suite configuration.”
The correct approach is to apply the restriction at the Postfix level only, using a dedicated OpenSSL configuration file. Do not make this change system-wide in /etc/ssl/openssl.cnf, as that would affect every application on the host that uses OpenSSL, not just Postfix.
Postfix 3.9 introduced the tls_config_file and tls_config_name parameters, which allow Postfix to load a dedicated OpenSSL configuration file and select a named application section within it. This scopes the restriction cleanly to Postfix alone.
Create the file:
vi /etc/postfix/tls_config.confWith this exact content, including the named section structure:
postfix = postfix_settings
[postfix_settings]
ssl_conf = postfix_ssl_settings
[postfix_ssl_settings]
system_default = postfix_tls_defaults
[postfix_tls_defaults]
SignatureAlgorithms = rsa_pss_rsae_sha512:rsa_pss_rsae_sha384:rsa_pss_rsae_sha256:RSA+SHA512:RSA+SHA384:RSA+SHA256:ECDSA+SHA512:ECDSA+SHA384:ECDSA+SHA256The section structure is critical. Without the named postfix application section at the top, OpenSSL has no context to apply the SignatureAlgorithms restriction on the server side, and the directive is silently ignored.
Then add both parameters to /etc/postfix/main.cf:
tls_config_file = /etc/postfix/tls_config.conf
tls_config_name = postfixtls_config_file tells Postfix which file to read; tls_config_name tells it which named section within that file to use. Both are required.
Reload and verify:
postfix reload
postconf tls_config_file
postconf tls_config_nameVerify the result
Test that your server correctly rejects a TLS 1.2 handshake offering only SHA1 signature algorithms:
openssl s_client -connect mx1.yourdomain.eu:25 -starttls smtp -no_tls1_3 2>&1 | grep -E "Cipher|Protocol"Replace mx1.yourdomain.eu with the hostname of your own mail server.
A successful TLS 1.2 connection should show an AES-GCM or CHACHA20 cipher, for example ECDHE-ECDSA-AES256-GCM-SHA384. The SHA384 at the end of that cipher name refers to the HMAC used for message authentication, not the handshake signature algorithm; it is not related to the SHA1 issue we are fixing here. What matters is that the cipher family is GCM or CHACHA20, not CBC. You can also check your Postfix logs during an internet.nl test run; connections using SHA1-only handshakes will appear as no shared cipher errors, which is the correct behaviour: the server is refusing to negotiate on those terms.
Bonus: hardening Dovecot TLS for IMAP and POP3
While internet.nl only tests SMTP, it makes sense to apply the same hardening principles to Dovecot for consistency across your mail stack. Testing with testssl.sh against port 993 confirmed an A+ rating with a score of 96 after these changes.
Dovecot 2.4 introduced a new native syntax for SSL configuration using an ssl_server {} block. The legacy flat directives like ssl_server_cert_file still work via automatic translation, but the native syntax is cleaner and more forward compatible.
For servers using the split configuration style (conf.d/10-ssl.conf), replace the entire file with:
###
### SSL/TLS Configuration
###
ssl = required
ssl_min_protocol = TLSv1.2
ssl_cipher_list = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl_server {
cert_file = /etc/ssl/acme/mx1.yourdomain.eu.fullchain
key_file = /etc/ssl/acme/mx1.yourdomain.eu.key
prefer_ciphers = server
}For servers using a monolithic dovecot.conf, replace the SSL section with the same block above. Replace mx1.yourdomain.eu with your actual mail server hostname.
Key points about this configuration:
ssl_cipher_list matches the Postfix tls_high_cipherlist exactly, using only ECDHE with AES-GCM and CHACHA20-POLY1305. DHE suites are intentionally omitted since ECDHE provides equivalent forward secrecy with better performance, and all modern mail clients support ECDHE.
ssl_server_dh_file is removed since DHE is not in the cipher list and the file is therefore never used.
prefer_ciphers = server inside the ssl_server {} block ensures Dovecot uses its own cipher order rather than the client’s, equivalent to Postfix’s tls_preempt_cipherlist = yes.
After editing, restart Dovecot and verify:
systemctl restart dovecot
doveconf ssl_cipher_list
doveconf -a | grep -A5 "ssl_server {"The output should show the correct cipher list and the ssl_server {} block with prefer_ciphers = server active.
To test externally, use testssl.sh against port 993:
./testssl.sh mx1.yourdomain.eu:993Replace mx1.yourdomain.eu with your mail server hostname. A correctly hardened server should achieve an A+ rating.
Summary of changes
| File | Change |
|---|---|
/etc/postfix/main.cf | Add tls_high_cipherlist with explicit GCM and CHACHA20 cipher list |
/etc/postfix/main.cf | Add tls_preempt_cipherlist = yes |
/etc/postfix/main.cf | Add tls_ssl_options = NO_COMPRESSION, NO_RENEGOTIATION |
/etc/postfix/main.cf | Add smtpd_client_event_limit_exceptions = $mynetworks, 62.204.66.0/24 |
/etc/postfix/main.cf | Add tls_config_file = /etc/postfix/tls_config.conf |
/etc/postfix/main.cf | Add tls_config_name = postfix |
/etc/postfix/tls_config.conf | Create with named section structure and SignatureAlgorithms directive |
/etc/dovecot/conf.d/10-ssl.conf | Replace with native Dovecot 2.4 ssl_server {} block syntax |
A note on troubleshooting
The SHA1 subtest is the trickiest part of this fix. The key insight, discovered through comparing notes with the mailcow community, is that tls_config_file alone is not sufficient. Without tls_config_name pointing to a named application section in the config file, OpenSSL applies the directives only to the client side and ignores them on the server side. Internet.nl will continue to flag SHA1 even though direct openssl s_client testing shows the server is rejecting SHA1-only handshakes.
The named section structure in tls_config.conf and the matching tls_config_name = postfix directive in main.cf are both required for the server-side restriction to take effect.
This guide is part of the ongoing Debian 13 mail server series on this blog.
