Bali, Indonesia
Insights

Postfix Won't Start After an OS Upgrade? Read journalctl Before Editing main.cf

October 27, 2026
0:00
You upgraded the server to the next distro release, the upgrade finished cleanly, and now mail isn't moving. systemctl status postfix says the service failed, or, more confusingly, says it's active while nothing listens on port 25. The tempting move is to open main.cf and start commenting out lines until something works. Before you do that, read what Postfix said when it refused to start, because it almost always names the exact problem. This matters at a support desk too. A lot of config damage after upgrades comes from well-meant guessing: someone removes a parameter that wasn't the problem, Postfix still won't start, and now there are two problems instead of one.
systemctl status shows the unit state and only the last handful of log lines. When a daemon dies during startup, those lines are often generic, something like the control process exiting with an error code. That tells you something failed, not what. There's a second trap on Debian and Ubuntu. There, postfix.service is usually a thin wrapper, and the real mail system runs as an instance unit called [email protected]. The wrapper can report active (exited) even when the instance behind it has failed. So if you only look at postfix.service, you might conclude Postfix is fine while nothing is actually accepting mail. Start by listing every Postfix-related unit so you know which one actually failed:
BASH
# Show all Postfix units and their state
systemctl list-units --all 'postfix*'

# Read recent journal entries for all of them, this boot only
journalctl -u 'postfix*' -b --no-pager -n 100

# Narrow down to errors and fatals
journalctl -u 'postfix*' -b --no-pager | grep -Ei 'fatal|error|panic'
If journalctl -u returns almost nothing, Postfix may be logging under its own syslog identifiers rather than the unit name. Running journalctl -b --no-pager | grep postfix catches those.
Postfix is fairly disciplined about log severity. Lines tagged warning: mean Postfix noticed something odd but carried on. Lines tagged fatal: mean it stopped. When a service won't start, you're looking for the first fatal: line, and the warnings right before it often explain the context. Illustrative output format (the parameter, map, and paths below are placeholders, and wording varies by version):
TEXT
postfix/postfix-script: warning: symlink leaves directory: /etc/postfix/./makedefs.out
postfix/postconf: warning: /etc/postfix/main.cf: unused parameter: example_old_param=yes
postfix/master: warning: using backwards-compatible default setting append_dot_mydomain=yes
postfix/postmap: fatal: unsupported dictionary type: mysql
postfix/postfix-script: fatal: the Postfix mail system is not running
In a log like this, the last line is the consequence, not the cause. The line that matters is the one about the unsupported dictionary type. Read upward from the final fatal until you hit the first one, and that's usually your root cause. One more thing to know: on some newer distro releases, a classic syslog daemon like rsyslog isn't installed by default. In that case /var/log/mail.log simply doesn't exist, and all Postfix logging lives in the journal. A missing mail.log is not a sign that Postfix logged nothing.
Several things look alarming after an upgrade but rarely stop Postfix from starting. The first is the backwards-compatible default setting warning. Postfix has a compatibility_level parameter. When newer Postfix releases change a default, they keep the old behavior for configs whose compatibility_level is lower than the release that changed it, and they log a warning so you know. These warnings are informational. Raising compatibility_level to silence them changes real behavior, such as relay restrictions, so treat that as a separate, deliberate change rather than part of fixing startup. The second is unused parameter warnings from postconf. These mean a parameter in main.cf isn't recognized by the installed version, usually because it was renamed or removed, or because it's a typo. An unused parameter is ignored, not fatal. It's still worth noting, because a removed parameter you relied on means its behavior silently went away. The third is a port conflict. If the journal shows a bind error on port 25, the fix is not in main.cf at all. Something else, often a second MTA pulled in as a dependency, is already listening. ss -ltnp shows which process owns the port.
Once you've filtered out the noise, the fatal line tends to fall into a few familiar patterns. Missing lookup table support is a common one. On Debian-family systems, support for map types like MySQL, PostgreSQL, PCRE, or LDAP ships in separate packages next to the main postfix package. A release upgrade can drop or fail to carry over one of those packages, and any main.cf line referencing mysql:/etc/postfix/... or pcre:/etc/postfix/... then becomes fatal. The fix is reinstalling the matching package, not rewriting the map. postconf -m lists the map types the installed build currently supports, which confirms this quickly. Stale compiled maps are another. Files like /etc/aliases.db or /etc/postfix/transport.db are generated from plain-text sources. If the upgrade changed the underlying database library or its format, Postfix may fail to open the old .db files and log an open or format error naming the file. Rebuilding them from source with postmap or newaliases is the usual fix. This rewrites only the generated file, but check that the plain-text source is still present and correct before you run it. Removed or renamed parameters can turn fatal when they're used in a way Postfix now rejects, especially in TLS settings. A newer OpenSSL can refuse old certificate keys, dropped protocol names, or cipher settings that used to work. The error usually mentions TLS, a certificate path, or a cipher setting directly. Finally, config merges. During the upgrade, the package manager may have asked whether to keep your main.cf or master.cf or take the maintainer's version. Keeping your old master.cf while daemon behavior changed, or accepting the new one and losing your customizations, both show up here. Look for .dpkg-dist, .dpkg-old, or .rpmnew files in /etc/postfix and diff them against what's live.
Before editing anything, let Postfix validate itself. These commands are read-only and safe to run on a broken system:
BASH
# Validate config, file ownership and permissions. Silence means no problems found.
postfix check

# Show only settings that differ from defaults, plus unused parameter warnings
postconf -n

# Installed version and current compatibility level
postconf mail_version compatibility_level

# Map types this build supports
postconf -m

# Leftover package config files to compare against
ls -l /etc/postfix/ | grep -Ei 'dpkg|rpmnew|rpmsave'
postfix check is the most useful one here. It runs the same sanity checks the startup script does, so if it prints a fatal, that's the same error that stopped the service, but now you can reproduce it without restarting anything. postconf -n gives you a short list of everything you've customized, which is a better starting point for review than scrolling through the whole main.cf. Edit only what the error points to. If the fatal names a map type, fix the package. If it names a file, fix that file. If it names a parameter, look that parameter up in the man page for the installed version (man 5 postconf) before changing it, since the replacement may behave differently.
After the change, verify in this order:
  1. Run postfix check again and confirm it prints nothing.
  2. Restart with systemctl restart postfix, then run systemctl list-units --all 'postfix*' and confirm the instance unit, not only the wrapper, is active.
  3. Run postfix status to confirm the master process is running.
  4. Run ss -ltnp and confirm the expected process is listening on port 25, and on 587 or 465 if you use submission.
  5. Follow the log live with journalctl -u 'postfix*' -f, send a test message out and one in, and watch for fresh warnings or deferrals.
If startup works but new warning: lines appear, note them rather than ignoring them. They're often the backwards-compatibility messages that tell you which defaults will change if you ever raise compatibility_level. The practical habit is simple: when Postfix won't start after an upgrade, the first command is journalctl, the second is postfix check, and the editor comes third. The fatal line almost always tells you whether you're dealing with a missing package, a stale map file, a removed parameter, or a config merge, and each of those has a narrow fix. Changing only what the log points to keeps a one-problem outage from turning into a config you no longer trust.