Running your own mail server on Ubuntu is a common homelab and small-business goal. Modoboa wraps Postfix, Dovecot, Nginx, Amavis, SpamAssassin, and ClamAV behind a Python web admin panel and webmail—so you do not have to wire every component by hand.
This guide walks through a full Modoboa install on Ubuntu: server prep, DNS, the official modoboa-installer, post-install domain and mailbox setup, client configuration, deliverability (SPF, DKIM, DMARC, PTR), testing, and the failures people hit most often in production threads and GitHub issues. I ran the installer on Ubuntu 25.04 and kept the real terminal output below so you can compare your VPS. Where something tripped me up—missing Radicale paths, proxy env vars breaking curl, the old modo check_mx command in outdated guides—you will find the fix in the same section.
Tested on: Ubuntu 25.04 (Plucky Puffin); kernel 6.14.0-37-generic.
/tmp mounted without noexec, a FQDN hostname before install, and database passwords without special characters (special chars can break the install). See the modoboa-installer README.
Quick command reference
| Task | Command |
|---|---|
| Set hostname | sudo hostnamectl set-hostname mail.example.com |
| Clone installer | git clone https://github.com/modoboa/modoboa-installer |
| Generate config | sudo python3 ./run.py --stop-after-configfile-check example.com |
| Install | sudo python3 ./run.py --interactive example.com |
| Debug install | sudo python3 ./run.py --interactive --debug example.com |
| Test outbound 25 | timeout 8 telnet gmail-smtp-in.l.google.com 25 |
| Generate DKIM keys | sudo /srv/modoboa/env/bin/python /srv/modoboa/instance/manage.py modo manage_dkim_keys |
| Repair / health check | sudo /srv/modoboa/env/bin/python /srv/modoboa/instance/manage.py modo repair |
| Mail log | sudo tail -f /var/log/mail.log |
What Modoboa includes
Modoboa is a free, open-source mail hosting platform (ISC license) written in Python. A typical install bundles:
| Component | Role |
|---|---|
| Postfix | SMTP (sending and receiving mail) |
| Dovecot | IMAP/POP3 (mailbox access) |
| Nginx | Web admin panel and webmail |
| PostgreSQL or MariaDB | Modoboa configuration and mailbox metadata |
| Amavis + SpamAssassin + ClamAV | Spam and virus filtering |
| OpenDKIM | DKIM signing (when enabled per domain) |
| Let's Encrypt (certbot) | TLS certificates (optional in installer.cfg) |
You manage domains, mailboxes, and aliases in the browser. End users can use webmail or clients like Thunderbird and Outlook. The same installer supports Ubuntu 20.04 LTS (Focal) and newer per the official modoboa-installer.
Prerequisites
Before you run the installer, confirm you have:
| Requirement | Details |
|---|---|
| OS | Clean Ubuntu 20.04 LTS or newer (64-bit); 22.04/24.04 LTS are common choices |
| RAM | 2 GB minimum; 3 GB+ recommended with antivirus scanning |
| CPU / disk | 1 vCPU is enough for light use; 20 GB+ disk for logs and mail spool |
| Domain | A domain you control (e.g. example.com) |
| Hostname | FQDN such as mail.example.com pointing to the server IP |
| DNS | Working A (and MX) records before Let's Encrypt and mail tests |
| Port 25 | Outbound SMTP open at your provider (many clouds block it by default) |
| Access | SSH with sudo (non-root user recommended) |
VPS providers and port 25
Inbound port 25 to your server is usually fine. Outbound port 25 is what providers block—and without it you cannot deliver mail directly to Gmail, Outlook, and others.
Providers often mentioned as allowing port 25 (verify current policy before you buy): OVH, Contabo, Kamatera, Hetzner, and similar bare-metal or niche VPS hosts. DigitalOcean, Vultr, and AWS commonly block outbound 25 unless you open a support ticket—and they may re-block later.
Test after install:
timeout 8 bash -c 'echo quit | telnet gmail-smtp-in.l.google.com 25'On my test host, outbound port 25 was open. You should see something like this when it works:
Trying 192.178.211.26...
Connected to gmail-smtp-in.l.google.com.
Escape character is '^]'.
Connection closed by foreign host.Connection timeout or Unable to connect means you need an unblock or an SMTP relay (e.g. SendGrid, Amazon SES). See Postfix relay restrictions if you route outbound mail through a provider.
Step 1 — Plan DNS records
Mail breaks when DNS is wrong. Set these before running the installer if you use Let's Encrypt.
Required before install
| Type | Name | Value | Notes |
|---|---|---|---|
| A | mail |
Your server IPv4 | e.g. mail.example.com → 203.0.113.10 |
| MX | @ |
mail.example.com |
Priority 0 or 10; hostname only, not an IP |
| AAAA | mail |
Your IPv6 (if used) | Required if the server sends via IPv6 |
Set hostname on the server
sudo hostnamectl set-hostname mail.example.comEnsure /etc/hosts resolves the FQDN (add only if missing):
grep -q 'mail.example.com' /etc/hosts || echo '127.0.1.1 mail.example.com mail' | sudo tee -a /etc/hostsVerify:
hostname -fYou want the FQDN you set—for example:
mail.example.comAfter install (deliverability)
You will add SPF, DKIM, and DMARC once the domain exists in Modoboa. PTR (reverse DNS) is set at your hosting provider, not your domain registrar—value should be mail.example.com.
Step 2 — Update Ubuntu and install dependencies
Log in via SSH and update packages:
sudo apt update
sudo apt upgrade -yInstall tools the installer needs:
sudo apt install -y git python3-virtualenv python3-pip curl gnupg2 dnsutils inetutils-telnetSee apt on Linux if you are new to package management on Ubuntu. curl is handy later when you probe the web UI from the shell.
Optional but useful if the installer fails on Cairo-related Python packages:
sudo apt install -y libcairo2-dev pkg-config python3-devBefore the first install, create the Radicale config directory. This one caught me on a retest—the installer can fail if /etc/modoboa-radicale does not exist yet:
sudo mkdir -p /etc/modoboa-radicaleAfter dependencies install, git --version on my host reported:
git version 2.48.1sudo user instead of using root for daily work: adduser deploy && sudo usermod -aG sudo deploy, then su - deploy.
Step 3 — Download and configure the Modoboa installer
Clone the official installer:
git clone https://github.com/modoboa/modoboa-installer
cd modoboa-installer
ls -l run.pyClone and listing looked like this on my machine:
Cloning into 'modoboa-installer'...
-rwxr-xr-x 1 user user 10336 ... run.pyGenerate installer.cfg (replace example.com with your apex domain, not mail.example.com):
sudo python3 ./run.py --stop-after-configfile-check example.comThe config check prints a short welcome and creates installer.cfg when it is missing:
Welcome to Modoboa installer!
Checking the installer...
Installer seems up to date!
Checks complete
Configuration file installer.cfg not found, creating new one.Edit the config:
sudo nano installer.cfgFresh installer.cfg files usually contain lines like these (sudo grep -E '^(hostname|type|engine)' installer.cfg):
hostname = mail.%(domain)s
type = amavis
type = self-signed
engine = postgresCommon installer.cfg settings
TLS — production (recommended):
[certificate]
type = letsencrypt
[letsencrypt]
email = [email protected]TLS — lab / internal only:
[certificate]
type = self-signedDatabase — PostgreSQL (default):
[database]
engine = postgres
host = 127.0.0.1
install = trueDatabase — MariaDB/MySQL:
[database]
engine = mysql
host = 127.0.0.1
install = trueUse a real email for Let's Encrypt; the default placeholder address will cause certificate issuance to fail. For more on TLS in front of Nginx, see install an SSL certificate on Nginx.
Step 4 — Run the installer
Confirm DNS for mail.example.com resolves to this server:
dig +short mail.example.com AThe command should print your server IP. Empty output means the A record is missing or not propagated yet.
Start the interactive install:
sudo python3 ./run.py --interactive example.comConfirm when prompted. On my test host (3 vCPU, 8 GB RAM), the full run.py pass took about 10 minutes end to end. Budget 15–25 minutes on a fresh 2 GB VPS—ClamAV definition downloads and pip compiles are usually the slow part.
Near the start of a successful run you should see the component list and install banners:
Your mail server will be installed with the following components:
fail2ban modoboa amavis clamav dovecot nginx postfix postwhite spamassassin uwsgi radicale opendkim
The process can be long, feel free to take a coffee and come back later ;)
Starting...
Generating new self-signed certificate
Installing fail2ban
Installing modoboa
Installing radicale
Installing uwsgi
Installing nginx
Installing postfix
Installing dovecot
Installing amavis
Installing spamassassin
Installing clamav
Installing opendkimWhen it finishes cleanly, the installer prints the admin URL and default password:
Congratulations! You can enjoy Modoboa at https://mail.example.com (admin:password)Verify services and web UI
After install, confirm core services and ports:
systemctl is-active nginx postfix dovecot uwsgi postgresql
ss -lnpt | grep -E ':443|:25|:587|:993'
curl --noproxy '*' -k -sI --resolve mail.example.com:443:127.0.0.1 https://mail.example.com/accounts/login/Use --noproxy '*' if your shell has http_proxy/https_proxy set—otherwise curl may fail against localhost. That bit me on a host with proxy env vars exported.
Core services on my install were all active:
nginx active
postfix active
dovecot active
uwsgi active
postgresql activeListening mail and web ports looked like this:
LISTEN 0 100 0.0.0.0:25 0.0.0.0:*
LISTEN 0 511 0.0.0.0:443 0.0.0.0:*
LISTEN 0 100 0.0.0.0:587 0.0.0.0:*
LISTEN 0 100 0.0.0.0:993 0.0.0.0:*
LISTEN 0 100 127.0.0.1:9999 0.0.0.0:*The login page returned HTTP/2 200 with a Modoboa title:
HTTP/2 200
server: nginx/1.26.3 (Ubuntu)
content-type: text/html; charset=utf-8Page title: Welcome to Modoboa. Modoboa version installed: 2.9.0. I did not run a full Gmail round-trip on the test domain—that needs real DNS and mailboxes in the admin UI (covered in Step 5).
If it fails, retry with debug output:
sudo python3 ./run.py --interactive --debug example.comReboot once to ensure all services start cleanly:
sudo shutdown -r nowDefault login
| Field | Value |
|---|---|
| URL | https://mail.example.com |
| Username | admin |
| Password | password |
Change the password immediately: Admin → Settings → Profile.
Step 5 — Add your domain and mailboxes
Add the mail domain
- Open the admin panel → Domains → Add.
- Enter
example.comas the domain name. - Enable DKIM signing (selector e.g.
modoboa, key length 2048). - Optionally create a domain admin; SMTP best practice expects a [email protected] address.
Create a mailbox
- Domains → select your domain → Mailboxes → Add → Account.
- Role: Simple user.
- Set username (e.g.
alice) and password.
Create an alias (optional)
Domains → your domain → Aliases → Add — e.g. [email protected] → [email protected].
Step 6 — Publish SPF, DKIM, and DMARC
These records live at your DNS host. They are what Gmail, Microsoft, and Yahoo check before accepting your mail.
SPF (TXT on apex domain)
| Name | Value |
|---|---|
@ |
v=spf1 mx ~all |
Tighten later with ip4:YOUR.SERVER.IP if you send only from this host.
Verify:
dig example.com TXT +shortUntil you publish your own record, dig against a reserved name like example.com may show the IANA placeholder:
"v=spf1 -all"Your domain should show your own SPF record after you publish it.
DKIM (TXT record)
In Modoboa: Domains → your domain → DNS → Show key. Copy the Bind/named format.
| Name | Value |
|---|---|
modoboa._domainkey |
Paste the key content (no extra quotes) |
If DKIM keys do not appear after several hours, generate them manually:
sudo /srv/modoboa/env/bin/python /srv/modoboa/instance/manage.py modo manage_dkim_keysYou may see harmless Django model warnings—keys still generate:
amavis.Msgrcpt.mail: (fields.W342) Setting unique=True on a ForeignKey ...
amavis.Quarantine.mail: (fields.W342) Setting unique=True on a ForeignKey ...DMARC (TXT record)
| Name | Value |
|---|---|
_dmarc |
v=DMARC1; p=none; pct=100; rua=mailto:[email protected] |
Start with p=none to collect reports; move to quarantine or reject when confident.
PTR (reverse DNS)
At your VPS panel, set reverse DNS for your IPv4 (and IPv6 if used) to mail.example.com.
Check:
dig -x YOUR.SERVER.IP +shortWhen PTR is set correctly at your host, you should get your mail hostname:
mail.example.com.Run Modoboa repair checks
After DNS propagates and you add domains in the admin UI, run:
sudo /srv/modoboa/env/bin/python /srv/modoboa/instance/manage.py modo repairTypical output flags objects that need an owner or alias—run this after DNS propagates and you add domains in the admin UI:
Checking for... Sometime objects have no owner....
Checking for... Sometime mailboxes have no alias....Older Modoboa guides mention modo check_mx; that subcommand is not available in Modoboa 2.9.x—use modo repair and dig instead.
Step 7 — Test sending and receiving
Webmail
Log out of the admin account. Log in as your mailbox user at the same URL. Compose a message to an external address (Gmail, etc.) and reply from outside to your new mailbox.
mail-tester.com
- Visit mail-tester.com.
- Send an email from your Modoboa account to the address shown.
- Review score and fix SPF, DKIM, PTR, or content issues reported.
Aim for 8/10 or higher before relying on the server for production mail.
Step 8 — Configure email clients (Thunderbird / Outlook)
Modoboa supports AutoMX for automatic client discovery; manual settings work everywhere.
| Setting | Incoming (IMAP) | Outgoing (SMTP) |
|---|---|---|
| Server | mail.example.com |
mail.example.com |
| Port | 993 (SSL/TLS) or 143 (STARTTLS) | 587 (STARTTLS) |
| Auth | Normal password | Normal password |
| Username | Full email address | Full email address |
Microsoft Outlook may require SMTPS on port 465—enable it in Postfix if clients cannot send.
Accept the Let's Encrypt certificate in the client; with self-signed certs you must trust the certificate manually.
Troubleshooting common Modoboa issues on Ubuntu
Greylisting delays inbound mail
Modoboa enables Postfix greylisting by default. Mail may arrive after a few minutes. Log hint:
postfix/postscreen: NOQUEUE: reject: ... 450 4.3.2 Service currently unavailableTo disable, comment out postscreen_* lines at the end of /etc/postfix/main.cf, then:
sudo systemctl restart postfixPolicy daemon not listening (port 9999)
If /var/log/mail.log shows:
warning: connect to 127.0.0.1:9999: Connection refusedRestart supervisor:
sudo systemctl restart supervisor
sudo ss -lnpt | grep 9999When policyd is running, port 9999 should be listening locally:
LISTEN 0 100 127.0.0.1:9999 0.0.0.0:*If still down, comment out check_policy_service inet:127.0.0.1:9999 in smtpd_recipient_restrictions inside /etc/postfix/main.cf and restart Postfix.
Web interface 502 or connection refused
sudo systemctl status nginx --no-pager
sudo nginx -t
sudo systemctl restart nginxA healthy config test looks like:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulCheck logs: /var/log/nginx/error.log and /var/log/mail.log.
Out of memory (ClamAV / database killed)
On 1 GB RAM, clamav-freshclam or ClamAV may loop or OOM-kill other services. Options:
- Upgrade to 2–3 GB RAM.
- Temporarily stop freshclam during install:
sudo systemctl stop clamav-freshclam. - Tune ClamAV memory limits for small VPS workloads.
Minimum RAM notes from community installs: ~1 GB without ClamAV, ~2 GB with ClamAV.
Installation fails (radicale / partial install)
Some installs failed when /etc/modoboa-radicale was missing. If the installer errors on calendar (Radicale) setup:
sudo mkdir -p /etc/modoboa-radicaleAfter creating it, ls should show the directory:
drwxr-xr-x 2 root root 4096 ... /etc/modoboa-radicaleRe-run with --debug. If a previous attempt left state behind, use a fresh VM or purge leftover packages (postfix, dovecot, nginx, postgresql, clamav, etc.) before retrying—the installer has no official uninstall command.
Let's Encrypt rate limits or wrong hostname
Ensure mail.example.com resolves publicly before install. After five failed attempts, Let's Encrypt may rate-limit—fix DNS, wait, and re-run certbot or the installer.
Renew TLS certificates
Modoboa installs certbot (often under /opt/certbot-auto). Add a daily cron as root:
sudo crontab -e@daily /opt/certbot-auto renew -q && systemctl reload nginx postfix dovecotAdjust the certbot path if yours differs: sudo find / -name '*certbot*' 2>/dev/null.
Port 25 blocked — use an SMTP relay
Configure Postfix to relay through a provider such as SendGrid when outbound 25 is blocked. See SendGrid Postfix integration for relay syntax and Postfix relay restrictions for policy context on this site.
Optional: reverse proxy and TLS offload
Homelab setups sometimes put HAProxy, Caddy, or Nginx in front of Modoboa on a single public IP. That is advanced: you must forward TCP 25, 587, 993, and 443 (or terminate TLS consistently) without breaking SMTP STARTTLS. Document your proxy config separately; the stock installer expects Nginx on the mail host.
Security and maintenance habits
- Change default
admin/passwordimmediately. - Keep Ubuntu updated:
sudo apt update && sudo apt upgrade. - Monitor blacklists and DMARC reports Modoboa can surface.
- Back up
/srv/modoboa, Postfix/Dovecot configs, and your database regularly. - Do not expose unused admin ports; use firewall rules allowing only 22, 25, 80, 443, 587, 993 as needed.
For timezone and system clock accuracy (important for TLS and mail timestamps), see set timezone on Ubuntu.
Summary
Modoboa on Ubuntu is one of the faster paths to a self-hosted mail stack: the installer handles Postfix, Dovecot, Nginx, filtering, and the web UI on Ubuntu 20.04 LTS and newer. On my test host the full install took about 10 minutes; plan 15–25 minutes on a fresh 2 GB VPS where ClamAV and pip have to download from scratch.
Success still depends on the boring parts—DNS (MX, A, PTR, SPF, DKIM, DMARC), a clean server with enough RAM, and outbound port 25 or a relay. After install, confirm the login page returns HTTP 200, change the default admin password, add domains and mailboxes in the admin UI, then test with mail-tester.com before you rely on the server for production mail. If outbound 25 is blocked, relay through SendGrid or SES rather than fighting your VPS provider.
Official references: Modoboa documentation and modoboa-installer on GitHub.









