How to Set Up an AlmaLinux 10 VPS as an Outbound-Only Mail Server with Postfix, DKIM, SPF, DMARC, and rDNS
This guide explains how to configure an AlmaLinux 10 VPS as a standalone outbound-only mail server for sending transactional email from addresses such as:
noreply@example.com
contact@example.com
The server will use:
Postfix - SMTP server
OpenDKIM - DKIM signing
SPF - Sender authorization
DMARC - Domain policy and reporting
rDNS/PTR - Reverse DNS identity
TLS - Encrypted SMTP submission
Example values used in this guide:
Domain: example.com
Hostname: mail.example.com
Server IP: 203.0.113.10
Senders: noreply@example.com, contact@example.com
OS: AlmaLinux 10
Replace these with your actual values.
1. DNS Records
Create the following DNS records.
A Record
mail.example.com. 3600 IN A 203.0.113.10
MX Record
Even for an outbound-only server, having an MX record is useful for domain hygiene and bounce handling.
example.com. 3600 IN MX 10 mail.example.com.
SPF Record
example.com. 3600 IN TXT "v=spf1 ip4:203.0.113.10 a:mail.example.com -all"
DMARC Record
Start with monitoring:
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com; adkim=s; aspf=s"
After verifying clean delivery, move to:
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com; adkim=s; aspf=s"
Eventually:
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc@example.com; adkim=s; aspf=s"
PTR / rDNS
PTR records are not configured in your normal DNS zone. They must be set by your VPS provider.
Ask the provider to set:
203.0.113.10 -> mail.example.com
Forward and reverse DNS should match:
mail.example.com -> 203.0.113.10
203.0.113.10 -> mail.example.com
This is critical for deliverability.
2. Set the Server Hostname
hostnamectl set-hostname mail.example.com
Update /etc/hosts:
cat > /etc/hosts <<'EOF'
127.0.0.1 localhost
203.0.113.10 mail.example.com mail
EOF
Verify:
hostname -f
Expected:
mail.example.com
3. Install Required Packages
dnf update -y
dnf install -y epel-release dnf-plugins-core
dnf config-manager --set-enabled crb || true
dnf install -y postfix opendkim opendkim-tools firewalld certbot mailx bind-utils policycoreutils-python-utils cyrus-sasl cyrus-sasl-plain
This setup does not require Dovecot unless you want to host inbound mailboxes.
4. Configure the Firewall
For outbound-only authenticated SMTP, open port 587.
systemctl enable --now firewalld
firewall-cmd --permanent --add-service=submission
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
Used ports:
587 SMTP submission
80 Let's Encrypt HTTP challenge
443 HTTPS / future MTA-STS / optional web services
Port 25 is still required for outbound direct-to-MX delivery. Many VPS providers block outbound port 25, so confirm this later.
5. Get a TLS Certificate
certbot certonly --standalone -d mail.example.com
Certificate paths:
/etc/letsencrypt/live/mail.example.com/fullchain.pem
/etc/letsencrypt/live/mail.example.com/privkey.pem
6. Configure Postfix
Back up the original config:
cp /etc/postfix/main.cf /etc/postfix/main.cf.bak
cp /etc/postfix/master.cf /etc/postfix/master.cf.bak
Replace /etc/postfix/main.cf:
cat > /etc/postfix/main.cf <<'EOF'
# Identity
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
# IPv4 only
inet_protocols = ipv4
inet_interfaces = all
# Outbound-only domain handling
mydestination = localhost.$mydomain, localhost
relay_domains =
# Local trusted network
mynetworks = 127.0.0.0/8
# Do not be an open relay
smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination
# TLS
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_tls_security_level = may
smtp_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_loglevel = 1
smtp_tls_loglevel = 1
# SASL authentication
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = cyrus
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
# Sender restrictions
smtpd_sender_login_maps = lmdb:/etc/postfix/sender_login_maps
smtpd_sender_restrictions = reject_sender_login_mismatch, check_sender_access lmdb:/etc/postfix/sender_access, reject
# No local mailbox delivery for example.com
local_recipient_maps =
unknown_local_recipient_reject_code = 550
# Limits
mailbox_size_limit = 0
message_size_limit = 52428800
# DKIM signing
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = inet:127.0.0.1:8891
# AlmaLinux 10 / Postfix LMDB maps
alias_maps = lmdb:/etc/aliases
alias_database = lmdb:/etc/aliases
# Misc
smtpd_banner = $myhostname ESMTP
biff = no
append_dot_mydomain = no
readme_directory = no
compatibility_level = 3.6
EOF
7. Configure Submission Port 587
Enable SMTP submission:
postconf -M submission/inet="submission inet n - n - - smtpd"
postconf -P 'submission/inet/syslog_name=postfix/submission'
postconf -P 'submission/inet/smtpd_tls_security_level=encrypt'
postconf -P 'submission/inet/smtpd_sasl_auth_enable=yes'
postconf -P 'submission/inet/smtpd_recipient_restrictions=permit_sasl_authenticated,reject'
Important: postconf -P does not accept whitespace inside service override values. Keep complex sender restrictions in main.cf, not in master.cf.
8. Restrict Allowed Sender Addresses
Only allow these sender addresses:
noreply@example.com
contact@example.com
Create /etc/postfix/sender_access:
cat > /etc/postfix/sender_access <<'EOF'
noreply@example.com OK
contact@example.com OK
EOF
postmap lmdb:/etc/postfix/sender_access
Create /etc/postfix/sender_login_maps:
cat > /etc/postfix/sender_login_maps <<'EOF'
noreply@example.com noreply
contact@example.com contact
EOF
postmap lmdb:/etc/postfix/sender_login_maps
This means:
SMTP username noreply -> can send only as noreply@example.com
SMTP username contact -> can send only as contact@example.com
9. Configure SASL Authentication
Enable and start SASL:
systemctl enable --now saslauthd
Create /etc/sasl2/smtpd.conf:
cat > /etc/sasl2/smtpd.conf <<'EOF'
pwcheck_method: saslauthd
mech_list: plain login
EOF
Create system users for SMTP authentication:
useradd -r -s /sbin/nologin noreply
passwd noreply
useradd -r -s /sbin/nologin contact
passwd contact
These are SMTP auth users, not mailbox users.
10. Configure OpenDKIM
Create DKIM key directory:
mkdir -p /etc/opendkim/keys/example.com
Generate DKIM key:
opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s mail
Set permissions:
chown -R opendkim:opendkim /etc/opendkim
chmod 600 /etc/opendkim/keys/example.com/mail.private
Configure /etc/opendkim.conf:
cat > /etc/opendkim.conf <<'EOF'
Syslog yes
UMask 002
Mode sv
Canonicalization relaxed/simple
SubDomains no
OversignHeaders From
Socket inet:8891@127.0.0.1
PidFile /run/opendkim/opendkim.pid
UserID opendkim:opendkim
KeyTable /etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable
ExternalIgnoreList /etc/opendkim/TrustedHosts
InternalHosts /etc/opendkim/TrustedHosts
EOF
Create DKIM map files:
cat > /etc/opendkim/KeyTable <<'EOF'
mail._domainkey.example.com example.com:mail:/etc/opendkim/keys/example.com/mail.private
EOF
cat > /etc/opendkim/SigningTable <<'EOF'
noreply@example.com mail._domainkey.example.com
contact@example.com mail._domainkey.example.com
EOF
cat > /etc/opendkim/TrustedHosts <<'EOF'
127.0.0.1
localhost
mail.example.com
example.com
203.0.113.10
EOF
chown -R opendkim:opendkim /etc/opendkim
11. Add the DKIM Record to DNS
Print the generated DKIM public key:
cat /etc/opendkim/keys/example.com/mail.txt
It will look similar to:
mail._domainkey IN TXT ( "v=DKIM1; k=rsa; "
"p=PUBLIC_KEY_HERE" )
For Cloudflare, create a TXT record like this:
Type: TXT
Name: mail._domainkey
Content: v=DKIM1; k=rsa; p=PUBLIC_KEY_HERE
TTL: Auto
Proxy status: DNS only
Do not include:
IN TXT
( )
quotation marks
comments after semicolons
12. Fix Alias Database Permissions
On AlmaLinux 10/Postfix LMDB setups, generate the aliases database cleanly:
chown root:root /etc/aliases
chmod 644 /etc/aliases
rm -f /etc/aliases.lmdb
postalias lmdb:/etc/aliases
chown root:root /etc/aliases.lmdb
chmod 644 /etc/aliases.lmdb
Then:
newaliases
13. Enable Services
systemctl enable --now postfix
systemctl enable --now opendkim
systemctl enable --now saslauthd
systemctl restart saslauthd postfix opendkim
Validate:
postfix check
postconf -n
Check that port 587 is listening:
ss -tulpn | grep master
Expected:
0.0.0.0:587
14. Test DNS
dig +short A mail.example.com
dig +short MX example.com
dig +short TXT example.com
dig +short TXT _dmarc.example.com
dig +short TXT mail._domainkey.example.com
dig -x 203.0.113.10 +short
Expected:
mail.example.com resolves to 203.0.113.10
PTR resolves back to mail.example.com
SPF exists
DKIM exists
DMARC exists
Test DKIM:
opendkim-testkey -d example.com -s mail -vvv
Expected result:
key OK
15. Test SMTP Authentication
Install Swaks:
dnf install -y swaks
Test sending as noreply@example.com:
swaks --to recipient@gmail.com \
--from noreply@example.com \
--server mail.example.com \
--port 587 \
--auth LOGIN \
--auth-user noreply \
--auth-password 'YOUR_PASSWORD' \
--tls
Test sending as contact@example.com:
swaks --to recipient@gmail.com \
--from contact@example.com \
--server mail.example.com \
--port 587 \
--auth LOGIN \
--auth-user contact \
--auth-password 'YOUR_PASSWORD' \
--tls
This should fail because the sender does not match the authenticated user:
swaks --to recipient@gmail.com \
--from contact@example.com \
--server mail.example.com \
--port 587 \
--auth LOGIN \
--auth-user noreply \
--auth-password 'YOUR_PASSWORD' \
--tls
16. Check Whether Outbound Port 25 Is Blocked
Direct-to-MX delivery requires outbound TCP port 25.
Test:
nc -vz gmail-smtp-in.l.google.com 25
nc -vz alt1.gmail-smtp-in.l.google.com 25
Also test non-SMTP outbound connectivity:
nc -vz smtp.gmail.com 587
nc -vz google.com 443
If 587 and 443 work but 25 times out, the VPS provider is blocking outbound SMTP.
Example blocked result:
Connection to gmail-smtp-in.l.google.com 25 failed: TIMEOUT
Connected to smtp.gmail.com 587
Connected to google.com 443
In that case, Postfix can accept mail locally or through submission, but it cannot deliver directly to Gmail, Outlook, Yahoo, or other MX servers.
17. Ask the VPS Provider to Unblock Port 25
Send this to the provider:
Please unblock outbound TCP port 25 for VPS IP 203.0.113.10.
Hostname: mail.example.com
PTR/rDNS requested: 203.0.113.10 -> mail.example.com
This server is used only for legitimate transactional outbound mail from:
noreply@example.com
contact@example.com
SPF, DKIM, DMARC, TLS, and sender restrictions are configured.
After the provider unblocks port 25, flush the Postfix queue:
postqueue -f
Watch logs:
journalctl -u postfix -f
18. Alternative: Use an SMTP Relay on Port 587
If the VPS provider refuses to unblock outbound port 25, use a transactional email relay such as:
Amazon SES
Mailgun
Postmark
SMTP2GO
Brevo
SendGrid
Configure Postfix relayhost:
postconf -e 'relayhost = [smtp-relay.example.net]:587'
postconf -e 'smtp_sasl_auth_enable = yes'
postconf -e 'smtp_sasl_password_maps = lmdb:/etc/postfix/sasl_passwd'
postconf -e 'smtp_sasl_security_options = noanonymous'
postconf -e 'smtp_tls_security_level = encrypt'
postconf -e 'smtp_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt'
Create credentials:
cat > /etc/postfix/sasl_passwd <<'EOF'
[smtp-relay.example.net]:587 USERNAME:PASSWORD
EOF
postmap lmdb:/etc/postfix/sasl_passwd
chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.lmdb
systemctl reload postfix
Flush the queue:
postqueue -f
19. Queue Management
Show queued mail:
mailq
Retry delivery:
postqueue -f
Delete all queued test emails:
postsuper -d ALL
20. Useful Logs
Postfix logs:
journalctl -u postfix -f
OpenDKIM logs:
journalctl -u opendkim -f
SASL logs:
journalctl -u saslauthd -f
Recent Postfix logs:
journalctl -u postfix -n 100 --no-pager
21. Application SMTP Settings
Use these settings in your application:
SMTP host: mail.example.com
SMTP port: 587
Security: STARTTLS
Username: noreply
Password: password for noreply user
From address: noreply@example.com
Or:
SMTP host: mail.example.com
SMTP port: 587
Security: STARTTLS
Username: contact
Password: password for contact user
From address: contact@example.com
Do not allow arbitrary sender addresses from applications.
22. Final Checklist
Before production use, confirm:
A record exists for mail.example.com
MX record points to mail.example.com
PTR/rDNS points server IP back to mail.example.com
SPF includes the server IP
DKIM TXT record exists and validates
DMARC record exists
Postfix is not an open relay
Only approved sender addresses are allowed
SMTP submission on port 587 requires authentication
Outbound port 25 is open, or relayhost is configured
TLS certificate is valid
DKIM signing works
Conclusion
A secure outbound-only mail server does not need full mailbox hosting. For transactional sending, the key pieces are:
Postfix for SMTP
OpenDKIM for signing
SPF, DKIM, and DMARC for domain authentication
rDNS/PTR for server identity
Port 587 for authenticated submission
Port 25 outbound for direct MX delivery
The most common blocker is not Postfix. It is VPS providers blocking outbound TCP port 25.
If outbound port 25 is blocked, direct delivery will fail with timeout errors. Either request that the provider unblock port 25, or configure Postfix to use a reputable SMTP relay over port 587.