how to host mailcow on vps wey gmail go accept
Check if outbound port 25 dey open before you install mailcow. Fix your DNS records so Gmail no go reject your mail with 550 5.7.25 PTR error.
Wetin you dey build
Full mail server for one box wey you own: SMTP for to send and receive mail, IMAP so your phone and laptop go dey sync, webmail client, and spam filter wey go check every message for both sides. mailcow-dockerized pack Postfix, Dovecot, Rspamd, SOGo webmail, MariaDB, Redis, and ACME client inside one Docker Compose stack, so the software no be the hard part. You go finish am for half an hour.
The hard part na everything wey surround am. Email na one service wey the rest of internet no trust brand-new server at all. The gap between "e dey work" and "Gmail dey swallow every message" na because of four DNS records and one IP-reputation setting wey you fit no control well. Read the prerequisites below before you rent anything. If, after you read dem, you decide say the reputation wahala no worth am, that one na correct answer — our rundown of what is actually worth self-hosting in 2026 dey list email under "only if you mean it" for exactly these reasons.
The prerequisites ARE the project
If you miss any one of these, mail go no reach destination. We list dem from the ones wey dey cause most wahala:
Outbound port 25 must be open. Your server dey use TCP port 25 deliver mail to Gmail and Microsoft. Many VPS and cloud providers dey block outbound 25 by default to fight spam. This block dey silent — nothing go error for boot, everything go look fine, but mail go just sit for queue forever. Test am before you install anything. If dem block am, the only fix na to open support ticket make your provider open am; some providers dey do am for old accounts, some no dey do am at all.
A clean IP with usable reputation. Recycled VPS IPs dey often dey on blocklists because of spam from previous tenant. Check yours for service like Spamhaus lookup or mxtoolbox before you start. If IP dey on list, mail rejection go happen and no code fit fix am.
Control of DNS plus a correct PTR record. You need to add records to your domain's zone, and you need reverse DNS (PTR) for the server's IP wey point back to your mail hostname. PTR dey almost never dey inside your DNS panel — e dey with whoever own the IP, so you must set am for your VPS provider's control panel or through ticket.
6 GiB RAM and 2 vCPU is the comfortable floor. mailcow minimum requirement na 6 GiB RAM plus 1 GiB swap for private install, but dem advise 8 GiB once small number of users start to use am. If you get below about 2.5 GiB, generate_config.sh go suggest make you disable ClamAV virus scanner so the kernel no go start to kill containers. Give am 20 GB of SSD to start.
A DNS name, not a bare IP. Pick hostname like mail.example.com. That single name go become your MAILCOW_HOSTNAME, your TLS certificate subject, your PTR target, and your SMTP banner. Make sure say you use am consistently everywhere.
Step 1 — Prove outbound port 25 open
Do this first. If eya fail, everything else na waste of time. From your new VPS, try to open SMTP conversation with real mail server:
sudo apt update && sudo apt install -y netcat-openbsd
nc -vz -w 5 gmail-smtp-in.l.google.com 25If e work, result go come out fast:
Connection to gmail-smtp-in.l.google.com (142.250.x.x) 25 port [tcp/smtp] succeeded!If port block, e go hang for full five seconds, den e go fail:
nc: connect to gmail-smtp-in.l.google.com port 25 (tcp) timed out: Operation now in progressThat timeout na the block. Na provider-side network filter be that, no be your firewall, so no local change fit fix am. Open ticket: "Please enable outbound TCP port 25 for my VPS at <IP>; I am running a legitimate mail server." No install mailcow until you see "succeeded". Note say inbound 25 (wey other servers dey reach you) na different path and e dey usually open — na outbound side providers dey throttle.
Step 2 — Set the DNS records now
DNS changes dey take time to propagate, so make sure say you publish everything wey you fit before you start installation. Assume say your domain na example.com, your mail host na mail.example.com, and the IP na 10.0.0.10. Inside your zone, create:
mail.example.com. A 10.0.0.10
mail.example.com. AAAA 2001:db8::10 ; only if you have IPv6
example.com. MX 10 mail.example.com.
example.com. TXT "v=spf1 mx -all"
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:postmaster@example.com"The SPF record dey tell system say "only my MX fit send mail for this domain, reject any other one". Start DMARC for p=none so you fit monitor reports without your own mail dey bounce; change am to p=quarantine and then p=reject once you confirm say alignment dey work well. Two records still dey missing by choice: DKIM, wey mailcow go generate for you for Step 6, and PTR, wey you must set now for your provider's panel.
Set the PTR (reverse DNS) for 10.0.0.10 to mail.example.com — the exact value of MAILCOW_HOSTNAME. Na this single record most people dey forget, and big providers dey reject you because of am. If your panel no get rDNS field, open ticket.
Step 3 — Install Docker
mailcow need Docker Engine wey get Compose v2 plugin. Use Docker official convenience script instead of Ubuntu docker.io package, because Ubuntu package no carry Compose plugin at all:
curl -fsSL https://get.docker.com | sudo sh
sudo docker compose versionYou go see Docker Compose version v2.x line. If docker compose version print docker: 'compose' is not a docker command, Docker Engine don install but Compose plugin no dey. Install the plugin from Docker repository — rerun the script wey dey above, or follow our Docker Compose basics guide, wey go help you setup both from Docker apt repository.
Step 4 — Clone mailcow and generate the config
cd /opt
sudo git clone https://github.com/mailcow/mailcow-dockerized
cd mailcow-dockerized
umask
sudo ./generate_config.shCheck if umask print 0022 first — mailcow no go allow build if file mask no correct, and fresh Ubuntu 24.04 root shell already give you 0022. The script go ask you one important thing: the fully qualified hostname. Type mail.example.com — the value must match your A record and PTR exactly. E go write mailcow.conf, na that single environment file the whole stack dey read. Open am if you need to change the web ports (HTTP_PORT, HTTPS_PORT) or if you want disable ClamAV for small box:
MAILCOW_HOSTNAME=mail.example.com
HTTP_PORT=80
HTTPS_PORT=443
SKIP_CLAMD=n # set to y to drop the virus scanner on a <2.5 GiB boxSKIP_FTS=y na the other lever for box wey RAM small: full-text search na the second thing wey dey chop memory according to mailcow docs, and if you skip am, you go only lose body-text search for webmail.
Leave HTTP_PORT=80 and HTTPS_PORT=443 alone unless another thing for host already dey use dem — mailcow built-in ACME client need port 80 to reach from internet to get certificate. Na why you no suppose run separate nginx-plus-Certbot setup for the same box; mailcow dey issue and renew its own TLS internally, and if second service dey occupy 80/443, e go break everything.
Step 5 — Start the stack and log in
sudo docker compose pull
sudo docker compose up -d
sudo docker compose psThe pull go fetch about two dozen images; give am few minutes. When docker compose ps show say every container running (or healthy), open https://mail.example.com for browser. The default admin login na username admin, password moohoo. Change that password sharp-sharp for admin UI under Access → Administrators. If browser warn you say NET::ERR_CERT_AUTHORITY_INVALID, e mean say ACME certificate never issue yet — check the ACME failure below before you think say e don spoil; self-signed placeholder na normal thing for first one or two minutes.
Step 6 — Add a domain, a mailbox, and publish DKIM
Inside admin UI, open the Mail Setup page (Configuration → Mail Setup). Under the Domains tab, click Add domain and type example.com. Next, under Mailboxes, click Add mailbox to create you@example.com with one password. This mailbox don ready and you fit use IMAP access am.
Now for the DKIM key. Go to Configuration → ARC/DKIM keys. mailcow fit don already generate one key when you add the domain. If e never happen, generate one for there — pick the domain, keep the selector dkim, choose 2048-bit, and click Add. Copy the long TXT value wey e show and publish am like dis:
dkim._domainkey.example.com. TXT "v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqh...long-key...QAB"mailcow Domains page get one DNS button wey go list every record wey e need. E go show green tick or red cross for every record wey don publish. Use am as your checklist — make sure every row turn green before you test if mail dey deliver. If you see red DKIM row after you publish, e mean say the key split into TXT chunks wrongly. 2048-bit key long pass 255-character limit for single TXT string, so paste am as one logical value and let your DNS host split am into chunks for you.
Step 7 — Test deliverability and chase 10/10
Go to mail-tester.com, copy the random address wey e show you, then send message to that address from your new mailbox — log in to SOGo webmail for https://mail.example.com/SOGo and send from there. After that, click "Then check your score".
Try reach 10/10. Common reasons why score dey drop:
- SPF not aligned — your
MX/SPF record missing or the sending IP no dey inside the list. Recheck the SPF TXT record. - DKIM signature does not verify — the
dkim._domainkeyTXT record missing, e still dey propagate, or e don spoil. This one na the most common mistake. - No PTR / PTR mismatch — reverse DNS no dey resolve to
mail.example.com. Fix am for your provider side. - Listed on a blocklist — na the old reputation of your IP. Request delisting or ask for cleaner IP.
No send real mail to Gmail or Outlook until this score reach 10/10. If score low and IP still new, dem go flag your domain on day one.
Step 8 — Connect a real mail client
Set Thunderbird, Apple Mail, or your phone to use these settings for the server. The server host na mail.example.com for all of dem:
- IMAP: port 993, SSL/TLS (or 143 with STARTTLS)
- SMTP submission: port 465, SSL/TLS (or 587 with STARTTLS)
- Username: the full address,
you@example.com - Password: the mailbox password wey you set
No ever try send mail through port 25 — that port na for server-to-server only. mailcow no dey allow authenticated submission for that port, so if client try use am, the server go reject am. If client show error Relay access denied, e mean say e dey try send through port 25 or without authentication; change am to 465 or 587 with your mailbox credentials.
Step 9 — Back up wetin really matter
mailcow dey provide backup script wey dey take snapshot every stateful volume. Run am to external disk or mounted remote:
sudo MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
./helper-scripts/backup_and_restore.sh backup allall dey capture six things, and if you lose any one of dem, you lose data: vmail (the actual mailboxes), crypt (the keys wey dey decrypt vmail — e no go work without am), mysql (the MariaDB wey hold domains, users, aliases and settings), redis (queue and cache state), rspamd (learned spam/ham), and postfix (the mail queue). E dey run inside helper container wey dey write compressed archives, so backups go stay consistent even when the stack dey live. Automate am with nightly cron job, and add --delete-days 14 to prune old sets. To restore, na the same script with restore, wey go list the snapshots and make you pick wetin you wan bring back. Backup wey you never test-restore na just hope, no be backup — do one dry run for scratch VPS.
Step 10 — Update on a schedule
mailcow dey update through its own script. This script go pull new code, migrate mailcow.conf, prefetch images, and restart containers for dis order:
cd /opt/mailcow-dockerized
sudo ./update.sh --check # reports whether an update exists, changes nothing
sudo ./update.sh # applies itMake you back up first (Step 9), because schema migration hard to reverse. Updates dey come many times and dem dey include security fixes for internet-facing daemons, so no let mail server stay for months without update. If update make one container unhealthy, sudo docker compose logs --tail=50 <service>-mailcow go name the daemon wey fail to come back.
A note on hardening
mailcow dey run its own netfilter service (netfilter-mailcow) wey dey ban any IP wey dey hammer the mail and webmail ports. So, the mail side dey safe out of the box. But dis one no cover SSH for the host itself. SSH still dey open and dem still dey try brute-force am. You suppose pair dis build with Fail2ban wey dey watch the SSH auth log and use key-only login. Make sure say mailcow admin UI get strong password, and if possible, hide am for public internet or put am behind VPN.
Failure modes, with the exact strings
Mail queues and never delivers. Run sudo docker compose exec postfix-mailcow postqueue -p, or check the mail queue for the admin UI; entries go sit deferred with:
status=deferred (connect to gmail-smtp-in.l.google.com[142.250.x.x]:25: Connection timed out)This mean say your provider block outbound port 25 (Step 1). No config fit fix am — open ticket. E no be DNS and e no be TLS; how you go know na the word timed out against a remote MX on port 25.
Gmail marks everything spam, or bounces it. Open the message for Gmail, click "Show original", and read the auth results. dkim=fail or dkim=none mean say your dkim._domainkey TXT record missing, spoil, or e never propagate — republish exactly wetin the ARC/DKIM page show and wait for the TTL. spf=fail mean say the SPF/MX records no cover your IP. Alignment na everything; if one check fail, email go land for spam.
Rejected by large providers on connect. Bounces or Postfix logs go carry Gmail's PTR rejection:
550-5.7.25 [10.0.0.10] The IP address sending this message does not have a PTR
550-5.7.25 record setup, or the corresponding forward DNS entry does not match
550 5.7.25 the sending IP. As a policy, Gmail does not accept messages from IPs
550 5.7.25 with missing PTR records.The 550 5.7.25 code mean say reverse DNS missing or e no match. Set the PTR for your IP to mail.example.com for your provider (Step 2). Forward (A) and reverse (PTR) must match, and both must name the same host wey mailcow dey use greet other servers.
Browser shows a certificate warning that never clears. The acme-mailcow container fail to get real cert. Check its log:
sudo docker compose logs acme-mailcow | tail -n 40If you see line like Cannot validate any hostnames, skipping Let's Encrypt for 1 hour. or challenge failure, e mean say port 80 no dey reachable from internet, or the A record no point to this server. Confirm say mail.example.com resolve to the box, open 80 and 443 for any host firewall, and make sure say nothing else dey bind those ports. After you fix the problem, restart the client with sudo docker compose restart acme-mailcow instead of waiting for the one-hour back-off.
FAQ
Self-hosting email really worth am?
If you want to own your data, get unlimited aliases, and get full control, then yes — mailcow give you professional stack for the price of one VPS. But deliverability na work wey no dey end: IP reputation, DNS alignment, and blocklist monitoring na something you must dey check always. If na for important business address wey single day for spam folder fit cost you money, then use managed provider be the better choice. Self-host only when you value control pass convenience and you ready to manage am.
How I go know if outbound port 25 dey blocked?
Run nc -vz -w 5 gmail-smtp-in.l.google.com 25 from the server. If you see "succeeded!", e mean say port open; if you see timed out after the pause, e mean say your provider dey block am. Na this one be the most common reason why self-hosted server fit receive mail but e no fit send am, and the only fix na for your provider to open the port — no local setting fit change am.
Why my mail still dey land for Gmail spam folder?
Na usually authentication chain wey don spoil. Use "Show original" for Gmail and look for spf=pass, dkim=pass, and dmarc=pass. If you see dkim=fail, e mean say dkim._domainkey TXT record missing or e don spoil; PTR mismatch or new IP wey no get sending history fit also cause wahala. Make sure you get 10/10 for mail-tester.com first, then start to warm up the IP slowly — send few messages every day and increase am gradually — no just blast many emails for day one.
Wetin exactly I need to back up?
Run backup_and_restore.sh backup all and carry the whole set commot from the server. E go capture vmail (the mailboxes), crypt (the keys wey dey decrypt dem), the MariaDB database (domains, users, aliases, settings), Redis, Rspamd's learned data, and the Postfix queue. People dey often forget the crypt volume — without am, the vmail backup na just ciphertext wey nobody fit read. Test how to restore am for another scratch box at least once.
I fit run mailcow for 2 GB VPS?
No, e no go easy. generate_config.sh go suggest make you disable ClamAV if you get less than 2.5 GiB, and even then, Rspamd, ClamAV, Dovecot and MariaDB all dey fight for memory, so you go hit swap and OOM kills when load increase. Use 6 GiB plus 1 GiB swap as the minimum for stable single-user install, and move to 8 GiB immediately when more than two people start to use am.