How to Run mailcow VPS Wey Gmail Go Accept
Test outbound port 25 before you install mailcow: block means mail go stay for queue. Set DNS properly to stop Gmail 550 5.7.25 PTR rejection.
Wetin you dey build
Complete mail server for box wey you own: SMTP to send and receive mail, IMAP so your phone and laptop stay in sync, webmail client, and spam filter wey dey score every message for both directions. mailcow-dockerized bundle Postfix, Dovecot, Rspamd, SOGo webmail, MariaDB, Redis and ACME client inside one Docker Compose stack, so na everything around the software be the hard part. You go get am running within half an hour.
The hard part na everything around am. Email na the one service wey the rest of internet actively distrust brand-new server, and the gap between “e dey work” and “Gmail silently dey eat every message” dey come down to four DNS records and one IP reputation setting wey you fit no fully control. Read the prerequisites below before you rent anything. If, after you read dem, you decide say the reputation grind no worth am, na valid answer be that. our rundown of wetin really worth self-hosting for 2026 put email under “only if you mean it” for exactly these reasons.
Prerequisites na the project
If you miss any one of these, mail wey you send fit never arrive. From the one wey dey cause problem pass to the one wey dey cause problem less:
Outbound port 25 must dey open. Your server dey deliver mail to Gmail and Microsoft through TCP port 25. Plenty VPS and cloud providers dey block outbound 25 by default to fight spam. The block dey silent. Nothing dey show error during boot, everything dey look healthy, but mail just dey stay for queue forever. Test am before you install anything. If e dey blocked, the only fix na to open support ticket and ask your provider to open am. Some providers go do am for old accounts, while some no go ever do am.
A clean IP wey get usable reputation. Recycled VPS IPs often dey on blocklists already because the previous tenant use dem for spam. Check your own IP with service like Spamhaus lookup or mxtoolbox before you commit. If the IP dey listed, dem fit reject your mail and you no fit code your way out of that problem.
Control of DNS plus correct PTR record. You need add records to your domain zone. You also need reverse DNS (PTR) for the server IP to point back to your mail hostname. PTR almost never dey set for your DNS panel. E dey with the person or company wey own the IP. So, set am from your VPS provider control panel or through support ticket.
6 GiB RAM and 2 vCPU na the comfortable floor. mailcow own minimum na 6 GiB RAM plus 1 GiB swap for private install. Dem advise 8 GiB once some users depend on am. Below about 2.5 GiB, generate_config.sh go offer to disable the ClamAV virus scanner so the kernel no go start kill containers. Give am 20 GB SSD to start.
A DNS name, no be bare IP. Choose hostname like mail.example.com. That one name go become your MAILCOW_HOSTNAME, your TLS certificate subject, your PTR target, and your SMTP banner. Keep am consistent everywhere.
Step 1, Confirm say outbound port 25 dey open
Do this one first. If e fail, everything else na wasted effort. From the fresh VPS, try 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 25Working result go show immediately:
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, then 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 cause am, 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 this command return "succeeded". Note say inbound 25, wey other servers use reach you, na separate path and e dey usually open. Na outbound side providers dey throttle.
Step 2, Set the DNS records now
DNS changes dey take time before dem propagate, so publish everything wey you fit before you install. Assume say your domain na example.com, your mail host na mail.example.com, and the IP na 10.0.0.10. For 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 talk say “na only my MX fit send for this domain; reject every other one”. Start DMARC with p=none so you fit monitor reports without bouncing your own mail; tighten am to p=quarantine, then p=reject after you confirm say alignment dey work. Two records still dey missing on purpose: DKIM, wey mailcow go generate for you for Step 6, and PTR, wey you go set now for your provider 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 plenty people dey forget, and big providers dey reject mail because of am. If your panel no get rDNS field, raise a ticket.
Step 3, Install Docker
mailcow need Docker Engine with the Compose v2 plugin. Use Docker official convenience script instead of Ubuntu docker.io package, because e no come with Compose plugin at all:
curl -fsSL https://get.docker.com | sudo sh
sudo docker compose versionYou suppose see one 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 there. Install the plugin from Docker repository, run the script above again, or follow our Docker Compose basics guide, wey set up both from Docker own 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 umask prints 0022 first, because mailcow no dey build with odd file mask. Fresh Ubuntu 24.04 root shell already dey give you 0022. The script go then ask the one thing wey matter: the fully qualified hostname. Enter mail.example.com. The value must match your A record and PTR exactly. E go write mailcow.conf, the only environment file wey the whole stack dey read. Open am if you need change the web ports (HTTP_PORT, HTTPS_PORT) or 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 control for low-RAM box. Full-text search na the second thing wey dey use plenty memory according to mailcow docs. If you skip am, na only body-text search for webmail you go lose.
Leave HTTP_PORT=80 and HTTPS_PORT=443 unless another thing for the host already dey use dem. mailcow built-in ACME client need port 80 to dey reachable from internet before e fit obtain the certificate. Na why you no run separate nginx-plus-Certbot setup for the same box. mailcow dey issue and renew im own TLS internally, and another service wey dey occupy 80/443 go break am. The same thing apply to any other service wey need public web front end. So setup like Halcyon wey dey redesign Jellyfin library as 90s video store, wey expect im own reverse proxy for 443, suppose dey another host. The exception na service wey never ask for public web port: self-hosted RustDesk relay dey listen only on im own 21115 to 21119 range, so e fit share the box if you get enough bandwidth headroom.
Step 5, Start the stack and log in
sudo docker compose pull
sudo docker compose up -d
sudo docker compose psThe pull dey fetch about two dozen images; give am some 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 immediately for the admin UI under Access → Administrators. If browser warn you about NET::ERR_CERT_AUTHORITY_INVALID, e mean say ACME certificate never issue yet. Check the ACME failure below before you assume say e don spoil; self-signed placeholder dey normal for the first minute or two.
Step 6, Add domain, mailbox, and publish DKIM
For the admin UI, open the Mail Setup page (Configuration → Mail Setup). For the Domains tab, click Add domain and enter example.com. Then for Mailboxes, click Add mailbox to create you@example.com with password. This mailbox don ready work and you fit reach am through IMAP.
Now, make the DKIM key. Go to Configuration → ARC/DKIM keys. mailcow fit don generate key when you add the domain. If e never do am, generate one there. Select the domain, leave the selector as dkim, choose 2048-bit, then click Add. Copy the long TXT value wey e show and publish am as:
dkim._domainkey.example.com. TXT "v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqh...long-key...QAB"mailcow Domains page get DNS button. This button list every record wey mailcow expect and show green tick or red cross based on wetin you actually publish. Use am as checklist. Make every row green before you test deliverability. If DKIM row still red after you publish am, e usually mean say you split the key wrongly across TXT chunks. 2048-bit key long pass the 255-character limit for one TXT string. Paste am as one logical value, then allow your DNS host split am into chunks.
Step 7, Test deliverability and chase 10/10
Go mail-tester.com, copy the random address wey e show, then send message go am from your new mailbox. Log in to SOGo webmail for https://mail.example.com/SOGo and send from there. Then click "Then check your score".
Target na 10/10. Na these things dey commonly reduce the score, and na wetin dem mean:
- SPF no align, your
MX/SPF record dey miss, or the sending IP no dey covered. Check the SPF TXT again. - DKIM signature no verify,
dkim._domainkeyTXT dey miss, e still dey propagate, or e don spoil. Na this one dey cause the most common failure. - No PTR / PTR mismatch, reverse DNS no resolve to
mail.example.com. Fix am for the provider. - Dem list am for blocklist, your IP get bad reputation from before. Request delisting, or ask for cleaner IP.
No send real mail go Gmail or Outlook until the result show 10/10. Low score plus new IP fit make dem flag your domain from day one.
Step 8, Connect real mail client
Point Thunderbird, Apple Mail, or your phone go the server with these settings. 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
Never send client mail through port 25. Na server-to-server traffic only for that port. mailcow no offer authenticated submission there, and e go refuse client wey point to am. If client report Relay access denied, e dey try send through 25 or without authentication; switch am to 465 or 587 with your mailbox credentials.
Step 9, Back up wetin really matter
mailcow get backup script wey dey take snapshot of every stateful volume. Run am go 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 any one lost, data go lost: vmail (the real mailboxes), crypt (the keys wey dey decrypt vmail; without am, dem useless), mysql (the MariaDB wey dey hold domains, users, aliases and settings), redis (queue and cache state), rspamd (spam/ham learning), and postfix (the mail queue). E dey run inside helper container wey dey write compressed archives, so backups dey consistent even while stack still dey run. Automate am with nightly cron job, and add --delete-days 14 to prune old sets. Cron job wey stop working fit fail silently, so send the script exit status go somewhere wey you go actually notice am; self-hosted ntfy server fit take one-line curl from the same cron entry and put the failure for your phone that night. Restore na the same script with restore. E go list the snapshots and let you choose wetin to bring back. Backup wey you never test-restore na hope, no be backup. Do one dry run go scratch VPS.
I step 10, Make updates follow schedule
mailcow dey update through its own script. The script dey pull new code, migrate mailcow.conf, prefetch images, and restart containers in the correct order:
cd /opt/mailcow-dockerized
sudo ./update.sh --check # reports whether an update exists, changes nothing
sudo ./update.sh # applies itBack up first (Step 9), because e hard to reverse schema migration. Updates dey come often and dem include security fixes for daemons wey face internet, so no let mail server stay for months without update. If update ever make one container become unhealthy, sudo docker compose logs --tail=50 <service>-mailcow go show the daemon wey fail to come back.
Hardening bad note
mailcow dey run im own netfilter service (netfilter-mailcow) wey dey ban IPs wey dey hammer mail and webmail ports, so mail side get protection out of the box. But e no cover SSH for the host itself. SSH still dey exposed and attackers still dey use brute-force attack against am. Pair this setup with Fail2ban wey dey monitor SSH auth log and key-only login. Use strong password protect mailcow admin UI. If possible, keep am off public internet or put am behind VPN.
Failure modes, with the exact strings
Mail queues and e no dey deliver. Run sudo docker compose exec postfix-mailcow postqueue -p, or read the admin UI mail queue; entries go sit as deferred with:
status=deferred (connect to gmail-smtp-in.l.google.com[142.250.x.x]:25: Connection timed out)Na your provider block outbound port 25 (Step 1). No config fit fix am, open ticket. E no be DNS and e no be TLS; the sign na word timed out against remote MX for port 25.
Gmail dey mark everything as spam, or e dey bounce am. Open the message for Gmail, choose "Show original", then read the auth results. dkim=fail or dkim=none mean say your dkim._domainkey TXT dey missing, spoil, or e never propagate finish. Publish 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 the main thing; one check wey fail fit make the message enter spam.
Large providers reject am during connection. Bounces or Postfix logs get Gmail 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 dey missing or e no match. Set PTR for your IP to mail.example.com for the provider (Step 2). Forward (A) and reverse (PTR) must agree, and both must name the same host wey mailcow dey use greet other servers.
Browser dey show certificate warning wey no dey clear. The acme-mailcow container fail to get real certificate. Check the log:
sudo docker compose logs acme-mailcow | tail -n 40Line like Cannot validate any hostnames, skipping Let's Encrypt for 1 hour. or challenge failure 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 through any host firewall, and make sure say nothing else dey bind those ports. After you fix the cause, restart the client with sudo docker compose restart acme-mailcow instead make you wait for the hour-long back-off.
FAQ
Self-hosting email really worth am?
If you want make data remain your own, unlimited aliases, and full control, yes, mailcow dey give you professional stack for the price of VPS. But deliverability na work wey no dey finish: IP reputation, DNS alignment, and blocklist monitoring dey continue always. For business address wey matter well-well, where one day for person spam folder fit cost you plenty, managed provider na the practical choice. Use self-hosting when control matter pass convenience and you ready to maintain am. If wetin you really want na make your data comot from another person server, instead of email specifically, start with service wey nobody else fit control: photo library no get deliverability problem at all, and our PhotoPrism and Immich comparison explain the RAM minimums and backup commands for that setup on the same kind VPS.
How I fit know whether outbound port 25 dey blocked?
Run nc -vz -w 5 gmail-smtp-in.l.google.com 25 from the server. "succeeded!" mean say e open; timed out after the pause mean say your provider block am. Na this be the most common reason why self-hosted server fit receive mail but e no fit send am, and na only your provider fit fix am by opening the port. No local setting fit change this.
Why my mail still dey enter Gmail spam folder?
Almost every time, na broken authentication chain cause am. Use "Show original" for Gmail and look for spf=pass, dkim=pass, and dmarc=pass. dkim=fail show say dkim._domainkey TXT record dey miss or e spoil; PTR mismatch or new IP wey get no sending history fit also cause problem. Make mail-tester.com reach 10/10 first, then warm the IP slowly. Send few messages per day and increase the volume gradually, instead of sending plenty from the first day.
Exactly wetin I need back up?
Run backup_and_restore.sh backup all and keep the complete set away from the server. E capture vmail (the mailboxes), crypt (the keys wey decrypt dem), MariaDB database (domains, users, aliases, settings), Redis, Rspamd learned data, and Postfix queue. The crypt volume na the one wey people dey overlook. Without am, vmail backup na unreadable ciphertext. Test restore onto scratch box at least once.
I fit run mailcow for 2 GB VPS?
E no go comfortable. generate_config.sh offer to disable ClamAV below about 2.5 GiB, and even then Rspamd, ClamAV, Dovecot and MariaDB go dey compete for memory. So you go hit swap and OOM kills under any real load. Treat 6 GiB plus 1 GiB swap as the minimum for stable single-user install, and move to 8 GiB once more than a couple people depend on am.