Workload · Sending your app's mail

Is port 25 open on your VPS? Your app's mail may not need it

Most apps don't need port 25 open on a VPS, because 25 is the port mail servers use to talk to each other, while an app sending through a mail service uses 587 or 465. You need 25 only if your own server will deliver straight to other people's mail servers. On a Primcast Cloud VPS, 465 and 587 work from day one and outbound 25 stays closed until you ask us to open it.

The short version
  1. 587 and 465 carry an app's mail; 25 is for server-to-server delivery
  2. Three commands show what your server can really reach
  3. A local Postfix queue survives a mail service's bad minute
  4. Own delivery needs reverse DNS and a clean address, not just a port

Which port does your mail really use?

It depends on who your server is talking to, and for most apps that's a mail service, not the recipient's mail server.

PortWho talks to whomSign-inEncryption
587Your app to a mail serviceUsername and password or keyStarts plain, then upgrades with STARTTLS
465Your app to a mail serviceUsername and password or keyEncrypted from the first byte
25One mail server to anotherNoneOffered between servers when both support it

Don't agonise over 587 against 465. RFC 8314 says that if the software is correct and both sides require TLS before any mail is handed over, there's no significant security difference between them. Use the one your mail service documents.

Why do hosts keep it closed?

Because a freshly rented machine with outbound 25 open can start sending spam at once, and the damage lands on the address and its neighbours. Large clouds publish the same rule for the same reason: they generally block outbound 25 by default because of the abuse risk, and leave the authenticated route open, on 587 and in some cases 465. The anti-abuse group M3AAWG first recommended this in 2005 for residential and dynamic addresses, and still calls blocking 25 from all but a few permitted hosts an industry best practice, and Spamhaus's Policy Blocklist exists to say certain addresses should send through an authenticated submission server instead of delivering directly.

So a closed port 25 says nothing bad about a host. The real questions are whether you need it and whether you can get it opened.

What can your server reach right now?

Find out before you change anything. On Ubuntu, if nc is missing, run apt-get update && apt-get install -y netcat-openbsd as root; other systems word the result differently. Replace smtp.provider.example with your mail service's host. The last line only opens a connection to one of Gmail's mail servers and sends nothing.

nc -4 -vz -w 5 smtp.provider.example 587
nc -4 -vz -w 5 smtp.provider.example 465
nc -4 -vz -w 5 gmail-smtp-in.l.google.com 25
You seeIt meansDo this
succeededThe path is openCarry on
timed outSomething on the way dropped it, usually a host block on 25On 25, use 587 or 465, or request 25. On 587 or 465, check your own firewall and the provider's host name
refusedUsually the far end answered and isn't listening there, though a firewall can also rejectCheck the hostname and port, then any firewall

Pick the route that matches your app

RoutePort 25 needed?What you look afterSuits
App signs in to a mail service directlyNoOne set of SMTP settingsOne app that can retry on its own
Local Postfix relays to a mail serviceNoA small queue on your serverSeveral apps, or code that shouldn't wait on the network
Your own mail server delivers directlyYesReputation, reverse DNS, listings, spam handlingYou have a reason to hold your own mail

For a password reset or an order receipt, the middle route is usually our pick. A web request that talks to a remote mail service directly can hang or fail when that service has a bad minute. Handing the message to Postfix on the same machine is quick, and Postfix keeps retrying. Your app connects to 127.0.0.1 on port 25, and that hop never leaves the machine, so a host block doesn't touch it.

Set up a Postfix relay on 587

These steps follow Postfix's own SASL guide, for Ubuntu 24.04, as root. Replace app.example.com, smtp.provider.example and you@yourdomain.example with your own. Use the server's own hostname as the mail name, not your main domain, or Postfix will try to keep mail for that domain on the machine. Once Postfix restarts in the next step, loopback-only means nothing outside the server can use it.

if echo "postfix postfix/mailname string app.example.com" | debconf-set-selections &&
   echo "postfix postfix/main_mailer_type select Internet Site" | debconf-set-selections &&
   apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y postfix libsasl2-modules &&
   postconf -e 'inet_interfaces = loopback-only' \
     'relayhost = [smtp.provider.example]:587' \
     'smtp_tls_security_level = encrypt' \
     'smtp_sasl_auth_enable = yes' \
     'smtp_sasl_tls_security_options = noanonymous' \
     'smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd' &&
   { [ -e /etc/postfix/sasl_passwd ] || install -m 600 /dev/null /etc/postfix/sasl_passwd; }; then
  echo "now add the login line"
else
  echo "STOP: Postfix is not set up"
fi

Open /etc/postfix/sasl_passwd in an editor rather than echoing the password, which would leave it in your shell history. Add one line, [smtp.provider.example]:587 username:password. The brackets and port must match relayhost exactly, as Postfix's guide warns. Then build the table, restart and send yourself a message:

if [ -s /etc/postfix/sasl_passwd ] && chmod 600 /etc/postfix/sasl_passwd &&
   postmap /etc/postfix/sasl_passwd && chmod 600 /etc/postfix/sasl_passwd.db &&
   postfix check && systemctl restart postfix; then
  printf 'From: you@yourdomain.example\nSubject: relay check\n\nHello from the server.\n' |
    sendmail -f you@yourdomain.example you@yourdomain.example
  sleep 10; journalctl -t postfix/smtp -n 5 --no-pager
else
  echo "STOP: check the login file and Postfix before sending"
fi

You're looking for status=sent. If the block says -- No entries -- or shows no status= line, run grep postfix/smtp /var/log/mail.log | tail -n 5. If mail sits in the queue, postqueue -p shows why. Three things usually catch people:

  • Only 465 on offer? Use :465 in both relayhost and the login line, run postconf -e 'smtp_tls_wrappermode = yes', then run the second block again to rebuild the table and restart. Without that setting Postfix waits for a greeting that never comes.
  • The From address is refused. Most mail services accept only domains you've verified with them, so root@ your hostname usually won't pass. Set a real sender in your app.
  • It arrives, in spam. Open the original message and read Authentication-Results. You want SPF and DKIM to pass, which comes from the DNS records your mail service gave you.

What if you really do need port 25?

Then you're running a mail server, and the port is the easy part. On a Cloud VPS, ask us to open it in chat or a ticket. Say that it's for your own mail server and which domain it serves. Before you send the first message, get these right:

  1. Reverse DNS. Gmail's rules for all senders ask for valid forward and reverse DNS, and the name on the address must resolve back to it. On whole servers you edit this from the dashboard. The Cloud VPS page doesn't list it among its controls, so raise it in the same message as port 25.
  2. SPF or DKIM, and TLS. Gmail asks for at least one of those two for all senders, plus a TLS connection. Set up both, and DMARC too.
  3. Listings. Check the address at check.spamhaus.org, which Spamhaus names as the only place for Policy Blocklist removals. It says a single static address that's a real outbound mail server, assigned to you, with proper forward and reverse DNS, can be removed there, if the network's policy allows it.
  4. IPv6. Your machine has IPv6 as well, and mail can leave over it. Give that address its own reverse DNS, or set inet_protocols = ipv4 in Postfix and restart it, until you have.

A new address also has no sending record, so no host can promise the inbox. Our mailcow guide goes through the rest of running a full mail system.

The server class that fits

OptionPort 25Choose it when
Cloud VPSOutbound 25 closed on new machines, opened on request; 465 and 587 open from day oneYour app sends through a mail service
Custom dedicated serverOpen from day oneMail is a main job of the machine (tell us when you order)
Web hostingNot your concern, there's no server to runYou want a website and email and nothing to look after

On whole servers the closing rule is narrow. If one of your addresses is listed for actually sending spam, outbound 25 on that one address closes for 30 days. A listing against a whole range, or a policy listing, never triggers it. Addresses there are checked against public blocklists before you get them and swept afterwards, and a listed one is swapped. If you just need people at your domain to have mailboxes, see email on your own domain first.

Questions people ask

Can I deliver mail over another port if 25 is closed?
Not directly to other people's mail servers, because they accept delivery on 25. What you can do is hand mail over 587 or 465 to a service that delivers it for you, which is the relay in this guide.
If I use a mail service, does my VPS need reverse DNS?
Not for that mail. The service's servers make the final delivery, so their addresses are the ones receivers judge. Your job is the SPF and DKIM records for your domain.
Does this apply to receiving mail too?
What's described here is outbound. If you want to receive mail on the server you're running a mail server, so mention that when you ask us about port 25.

Get your app's mail moving

Pick a Cloud VPS, point your app at a mail service on 587, and ask us about port 25 only if you need it.

Choose a Cloud VPS

Read next