
Email from a fresh VPS fails for predictable reasons. You may have no reverse DNS, weak auth, or an IP with no sending history.
In 2026, the low-risk fix for most stacks is an SMTP relay. Your server submits mail to a trusted provider. Your apps still send locally.
This SMTP relay setup guide tutorial walks through a clean Postfix relay on Ubuntu. It also covers DNS records and tests that prevent the usual “550/554” surprises.
This is for VPS (or dedicated) admins who want WordPress contact forms, WooCommerce receipts, app alerts, and cron reports to arrive consistently.
If you run a multi-tenant cPanel box, the ideas still apply. You will usually enable relaying in the control panel instead of editing Postfix directly.
What you’ll build: a local Postfix “send-only” relay
You’ll configure Postfix to accept mail from local apps. Postfix then forwards it to an upstream SMTP provider (a smart host).
The provider handles outbound reputation and deliverability. Your VPS stays boring. That’s exactly what you want.
- Apps send to: localhost:25 (or sendmail)
- Postfix forwards to: smtp.provider.example:587 using STARTTLS + authentication
- DNS you’ll set: SPF + DKIM + DMARC aligned to your From domain
If you’re starting from a brand-new VPS, build on a hardened baseline first.
HostMyCode customers usually pair this with sane SSH and firewall defaults.
See our internal guide: Server hardening tutorial (2026).
Prerequisites (keep this tight)
- Ubuntu 24.04 LTS or Ubuntu 22.04 LTS VPS (root or sudo access)
- A domain you control (example.com) and access to its DNS zone
- Credentials for an SMTP relay provider (hostname, port, username, password or API key)
- Outbound connectivity from your VPS on port 587 (and optionally 465)
If you need a stable host for this setup, a HostMyCode VPS gives you root access for Postfix, DNS verification, and log-driven troubleshooting—without shared-hosting constraints.
Step 1: Pick the right relay mode (and avoid the common trap)
Most relay setups fall into one of these patterns:
- Provider-managed DKIM (recommended): You send through the provider and publish the DKIM key(s) they give you. This is the default choice for most teams.
- Self-managed DKIM on your VPS: You sign mail locally with OpenDKIM, then relay it. Choose this if you need DKIM keys to live on your server.
The trap is alignment. You can’t casually mix “From: yourdomain.com” with provider-managed “MAIL FROM” domains that don’t align.
DMARC checks alignment. Misalignment is a fast path to spam placement or rejections.
If your provider uses a different bounce domain, configure a custom return-path domain with them.
If you can’t, keep DMARC at p=none while you confirm alignment. Tighten it later.
Step 2: Install Postfix and set it to “Internet Site” (send-only)
Postfix installs cleanly on Ubuntu. Even for a relay-only box, you typically select “Internet Site”.
After that, block inbound SMTP from the internet with firewall rules and Postfix settings.
sudo apt update
sudo apt install -y postfix mailutils ca-certificates
During the Postfix prompt:
- General type of mail configuration: Internet Site
- System mail name: example.com (or a hostname like mail.example.com)
Confirm Postfix is running:
systemctl status postfix --no-pager
Step 3: Configure Postfix to relay through your SMTP provider
Edit /etc/postfix/main.cf. The goal is simple.
Set the relayhost, force TLS, enable auth, and accept mail only from localhost.
sudo nano /etc/postfix/main.cf
Add (or adjust) these lines. Replace values in brackets with your provider details.
# Identity
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
# Only listen locally (prevents public inbound SMTP)
inet_interfaces = loopback-only
# Relay all outbound mail through provider
relayhost = [smtp.provider.example]:587
# Restrict local clients (localhost only)
mynetworks = 127.0.0.0/8 [::1]/128
# TLS settings for outbound
smtp_tls_security_level = encrypt
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_loglevel = 1
# SASL auth for relay
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous
# Sensible limits
message_size_limit = 26214400
Create the password map file /etc/postfix/sasl_passwd:
sudo nano /etc/postfix/sasl_passwd
Example (use the provider’s exact host/port and credentials):
[smtp.provider.example]:587 relay-user@example.com:YOUR_APP_PASSWORD_OR_API_KEY
Secure it and build the hash database:
sudo chmod 600 /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd
Reload Postfix:
sudo systemctl restart postfix
Step 4: Open only what you need (UFW example)
If Postfix only listens on loopback, you don’t need inbound port 25 open to the internet.
You do need outbound 587 to the provider. Outbound traffic is usually allowed by default.
Check UFW status:
sudo ufw status verbose
If you previously allowed public SMTP, remove it:
sudo ufw delete allow 25/tcp
It’s easy to over-tighten a firewall on a new VPS. That can break mail. It can also break SSH.
If you need a safe recovery path, see: UFW firewall troubleshooting tutorial (2026).
Step 5: Publish SPF for your domain (minimum viable, then refine)
SPF tells receivers which systems may send mail for your domain. If you relay through a provider, SPF should authorize the provider.
In most cases, SPF should not authorize your VPS IP.
Create (or update) a TXT record for example.com:
Type: TXT
Name: @
Value: v=spf1 include:spf.provider.example -all
Notes:
- Use the provider’s SPF include string (they document it). Don’t guess.
- Only use
-allonce you’re sure nothing else sends as your domain. - If you also send from Google Workspace / Microsoft 365, include them too (and watch the 10-DNS-lookup SPF limit).
Step 6: DKIM signing (provider-managed first, then optional OpenDKIM)
For most relay providers, DKIM is a DNS change. You publish their public key, and they handle signing.
It’s also the quickest way to pass authentication checks.
Option A (recommended): provider-managed DKIM
Your provider will give you a selector and a TXT record, something like:
Type: TXT
Name: s1._domainkey
Value: v=DKIM1; k=rsa; p=MIIBIjANBgkqh...IDAQAB
Once DNS propagates, you’ll confirm it with a mailbox test in Step 8.
Option B: sign mail on your VPS with OpenDKIM (advanced, but practical)
Choose this if your provider can’t manage DKIM for your domain.
It also fits policies where keys must stay on your server.
sudo apt install -y opendkim opendkim-tools
Create a key directory:
sudo mkdir -p /etc/opendkim/keys/example.com
sudo chown -R opendkim:opendkim /etc/opendkim
sudo chmod go-rwx /etc/opendkim/keys
Generate a 2048-bit key (good default in 2026):
cd /etc/opendkim/keys/example.com
sudo opendkim-genkey -b 2048 -s s1 -d example.com
sudo chown opendkim:opendkim s1.private s1.txt
Wire OpenDKIM config:
sudo nano /etc/opendkim.conf
Syslog yes
UMask 002
Canonicalization relaxed/simple
Mode sv
SubDomains no
AutoRestart yes
AutoRestartRate 10/1h
OversignHeaders From
KeyTable /etc/opendkim/key.table
SigningTable /etc/opendkim/signing.table
ExternalIgnoreList /etc/opendkim/trusted.hosts
InternalHosts /etc/opendkim/trusted.hosts
Socket local:/run/opendkim/opendkim.sock
Create the tables:
sudo nano /etc/opendkim/trusted.hosts
127.0.0.1
localhost
*.example.com
sudo nano /etc/opendkim/key.table
s1._domainkey.example.com example.com:s1:/etc/opendkim/keys/example.com/s1.private
sudo nano /etc/opendkim/signing.table
*@example.com s1._domainkey.example.com
Connect Postfix to OpenDKIM by adding to /etc/postfix/main.cf:
# OpenDKIM milter
milter_default_action = accept
milter_protocol = 6
smtpd_milters = local:/run/opendkim/opendkim.sock
non_smtpd_milters = $smtpd_milters
Enable and restart services:
sudo systemctl enable --now opendkim
sudo systemctl restart postfix
Publish the DKIM TXT record shown in /etc/opendkim/keys/example.com/s1.txt.
Step 7: Add DMARC (start with monitoring, then enforce)
DMARC tells receivers what to do if SPF or DKIM fails. Start in monitor mode first.
Review reports, then enforce once you see clean alignment.
Create a TXT record for _dmarc.example.com:
Type: TXT
Name: _dmarc
Value: v=DMARC1; p=none; adkim=s; aspf=s; rua=mailto:dmarc@example.com; fo=1
Practical tips:
p=nonekeeps delivery stable while you validate alignment.- Create
dmarc@example.comfirst, or pointruato a mailbox you actually read. - After things settle, move to
p=quarantine, thenp=reject.
Step 8: Send test mail and read the logs like an operator
Send a quick message from the VPS:
echo "SMTP relay test $(date -Is)" | mail -s "Relay test" you@yourmailbox.com
Watch mail logs in real time:
sudo tail -f /var/log/mail.log
You’re looking for:
- Successful TLS negotiation to the relay host
- Authentication success (SASL)
- A
status=sentline with the provider’s response
If mail stalls, check the queue:
mailq
Then inspect a stuck message:
postcat -q QUEUEID
For timeouts, auth errors, and TLS issues that don’t resolve quickly, follow the diagnostic flow here: SMTP troubleshooting tutorial (2026).
Step 9: Make WordPress use your relay (two clean approaches)
WordPress defaults to PHP’s mail(). On many VPS stacks, that already routes into Postfix.
The rough edge is consistency. “From” headers can vary. Some plugins produce odd Return-Path values.
Approach A: Leave WordPress alone (uses local Postfix)
This is the lowest-friction option if your apps already use /usr/sbin/sendmail.
It also works best when your From domain matches your DNS records.
Confirm PHP knows the sendmail path:
php -i | grep -i sendmail_path
Approach B: Use an SMTP plugin to set consistent From + TLS
If you need per-site credentials, enforced From addresses, or you’re dealing with a mixed environment, use an SMTP plugin.
Point it to localhost (Postfix) or to the provider directly.
On a VPS, localhost is often simpler because credentials stay on the server.
If you’re still building your WordPress VPS stack, this pairs well with the relay steps above: WordPress VPS setup guide tutorial (2026).
Step 10: Add safety rails (rate limits, sender control, and secrets hygiene)
Mail systems attract abuse. Even a relay-only host needs guardrails.
The goal is simple. One mistake should not become a billing problem or a reputation problem.
Lock down who can submit mail
Setting inet_interfaces = loopback-only is the big one. It stops the public internet from talking to your Postfix SMTP daemon.
If you must accept mail from another internal host (for example, a separate app server), do it deliberately.
Use private networking plus explicit IP allowlists. Don’t open SMTP publicly “just to test”.
Protect relay credentials
- Keep
/etc/postfix/sasl_passwdat0600. - Use provider “app passwords” or restricted API keys where possible.
- Rotate credentials if the server is compromised.
Stop noisy scripts from flooding your relay
On multi-site servers, a broken contact form can send thousands of messages in minutes.
Add application-level throttling where you can. Also watch for queue spikes.
On cPanel/WHM servers, rely on the built-in limits. That helps one account avoid torching your reputation.
If you sell shared hosting plans, also review which features clients can toggle.
This guide helps reduce “creative” plugin installs: WHM Feature Manager Tutorial (2026).
Step 11: Quick deliverability checklist (use this before you blame the provider)
- From domain matches: your visible From header is
@example.com - SPF passes: includes your relay provider
- DKIM passes: provider DKIM record published (or OpenDKIM signing works)
- DMARC starts at p=none: then tighten after validation
- PTR isn’t required for relay-only: but your VPS hostname should still be sane
- No public SMTP exposure: Postfix listens on loopback or private IP only
- Logs are clean: no auth failures, no repeated deferrals
Common errors and fixes (fast mapping)
- 535 Authentication failed: wrong username/password, or provider requires an app password; re-check
/etc/postfix/sasl_passwdand rebuild withpostmap. - Connect to smtp.provider:587 timed out: outbound port blocked by firewall or upstream network; verify
nc -vz smtp.provider.example 587. - “TLS is required” or handshake errors: ensure
smtp_tls_security_level = encryptand your CA bundle exists at/etc/ssl/certs/ca-certificates.crt. - Mail lands in spam: SPF/DKIM/DMARC alignment issue, or From address mismatch; inspect headers in the received message.
For bounces and rejections, it helps to map the exact error codes to fixes.
This guide focuses on the 550/554 patterns you’ll see in logs: Email bounce troubleshooting tutorial (2026).
If you want reliable outbound mail without operating a full mail server, run this relay on a VPS you control. Start on a HostMyCode VPS, or hand it to our team with managed VPS hosting if you’d rather have a monitored, maintained configuration.
FAQ
Do I need reverse DNS (PTR) if I use an SMTP relay?
Usually no. Receivers evaluate the relay provider’s sending IPs and PTR. PTR still matters if you send directly from your VPS IP.
Should Postfix listen on the public interface?
Not for a relay-only setup. Keep inet_interfaces = loopback-only so the internet can’t submit mail to your server.
Can I use this on a dedicated server with multiple websites?
Yes. It’s a common pattern for multi-site hosting. Add monitoring for queue growth and consider per-app rate limiting to prevent accidental floods.
What’s the safest DMARC policy to start with?
p=none. Once SPF and DKIM pass consistently for your real traffic, move to p=quarantine and then p=reject.
Summary: stable mail delivery without turning your VPS into a mail platform
An SMTP relay keeps your server focused on running apps, not building email reputation.
Your apps send locally. Postfix forwards securely. Your domain proves authenticity with SPF/DKIM/DMARC.
If something breaks, you get clear logs and a tight failure surface.
If this is for production sites, run it on a VPS with predictable resources and full root access.
A HostMyCode VPS fits well here, and managed VPS hosting is a good option if you want ongoing patching and operator-level troubleshooting.