Running a CyberPanel mail server with acme.sh and Cloudflare

cyberpanel vps acme ssl mail setup

This is the full route to a CyberPanel mail server with valid TLS: Cloudflare DNS, one Let’s Encrypt certificate for mail.example.com issued by acme.sh, Postfix and Dovecot both serving it, and automatic renewal.

Replace example.com with your domain and SERVER_IP with your server’s public IP. Run every command as root.

1. Create the mailbox in CyberPanel

  1. Log in at https://SERVER_IP:8090.
  2. Open Websites → List Websites and confirm example.com is listed (create it if not).
  3. Open Email → Create Email Account, choose the domain, type a username (for example sales) and set a strong password.
  4. Click Create Email. The address is now [email protected].

Menu names can differ slightly between CyberPanel versions.

2. Set up DNS in Cloudflare

In the Cloudflare dashboard open example.com → DNS → Records and add:

TypeNameContentProxy status
AmailSERVER_IPDNS only (grey cloud)
MX@mail.example.com, priority 10DNS only
TXT@v=spf1 mx ~allDNS only
TXT_dmarcv=DMARC1; p=none; rua=mailto:[email protected]DNS only
TXTdefault._domainkeyDKIM value from CyberPanelDNS only

Cloudflare’s proxy only handles web traffic. Keep the mail record on the grey cloud: an orange cloud breaks IMAP, SMTP and certificate issuance.

For the DKIM value, open Email → DKIM Manager in CyberPanel, select the domain, generate the key and copy the TXT value. Use the record name CyberPanel shows (normally default._domainkey).

Also make sure ports 80, 25, 465, 587 and 993 are open, both in CyberPanel’s firewall and at your hosting provider.

Check the records from the server. The first result must equal the last:

dig +short mail.example.com
dig +short MX example.com
dig +short TXT example.com
dig +short TXT _dmarc.example.com
curl -4 ifconfig.me

If dig prints nothing, wait for DNS to update before going further.

3. Install acme.sh

Skip the first command if acme.sh is already installed. Version 3.x defaults to ZeroSSL, so set Let’s Encrypt as the default CA.

curl https://get.acme.sh | sh -s [email protected]
/root/.acme.sh/acme.sh --version
/root/.acme.sh/acme.sh --set-default-ca --server letsencrypt

4. Issue the certificate

Pick one option. Option B is better for renewals because it never touches the web server.

Option A: standalone (briefly stops OpenLiteSpeed)

Port 80 must be free. On CyberPanel, systemctl stop lsws can leave worker processes running, so stop and verify:

systemctl stop lsws
/usr/local/lsws/bin/lswsctrl stop
sleep 2
ss -tlnp | grep ':80 '        # must print nothing
pkill -9 litespeed            # only if something is still listening

/root/.acme.sh/acme.sh --issue --standalone -d mail.example.com --server letsencrypt \
  --pre-hook  "systemctl stop lsws; /usr/local/lsws/bin/lswsctrl stop; sleep 2" \
  --post-hook "systemctl start lsws"

systemctl start lsws          # always bring the websites back, even if issuing failed

The hooks are saved, so every automatic renewal frees port 80 first and restarts the web server afterwards. Without them, renewal fails because OpenLiteSpeed holds port 80.

Option B: DNS-01 through the Cloudflare API (no downtime)

In Cloudflare create an API token: My Profile → API Tokens → Create Token → Edit zone DNS, limited to the example.com zone. Then:

export CF_Token="your-cloudflare-api-token"
/root/.acme.sh/acme.sh --issue --dns dns_cf -d mail.example.com --server letsencrypt

acme.sh stores the token and reuses it for renewals. Port 80 and the web server are not involved.

5. Install the certificate to a stable path

Don’t point services at acme.sh’s internal folders. Copy the files out and register a reload command, so each renewal is applied automatically:

mkdir -p /etc/ssl/mail.example.com
/root/.acme.sh/acme.sh --install-cert -d mail.example.com --ecc \
  --key-file       /etc/ssl/mail.example.com/privkey.pem \
  --fullchain-file /etc/ssl/mail.example.com/fullchain.pem \
  --reloadcmd      "systemctl reload postfix; systemctl reload dovecot"
chmod 600 /etc/ssl/mail.example.com/privkey.pem
ls -l /etc/ssl/mail.example.com/

6. Point Postfix at it

Use postconf -e rather than editing main.cf by hand. Clear the legacy cert settings, or Postfix ignores smtpd_tls_chain_files. Leave the SNI map empty: a single hostname doesn’t need one.

cp /etc/postfix/main.cf /etc/postfix/main.cf.bak

postconf -e "smtpd_tls_chain_files = /etc/ssl/mail.example.com/privkey.pem, /etc/ssl/mail.example.com/fullchain.pem"
postconf -e "smtpd_tls_cert_file ="
postconf -e "smtpd_tls_key_file ="
postconf -e "tls_server_sni_maps ="

postconf smtpd_tls_chain_files smtpd_tls_cert_file smtpd_tls_key_file tls_server_sni_maps
postfix check && systemctl restart postfix
tail -15 /var/log/mail.log

The log should show no malformed BASE64 or legacy … will be ignored warnings. postfix.service showing active (exited) is normal on Ubuntu; the real daemon is postfix@-.

7. Point Dovecot at it

Dovecot has a global default certificate, plus optional local_name blocks per hostname. Set the global default to the mail certificate so every client gets a matching one, whatever name or SNI it sends:

cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.bak

grep -n "^ssl_cert\|^ssl_key\|local_name" /etc/dovecot/dovecot.conf
grep -rn "letsencrypt/live/mail" /etc/dovecot /etc/postfix

sed -i 's|^ssl_cert *=.*|ssl_cert = </etc/ssl/mail.example.com/fullchain.pem|; s|^ssl_key *=.*|ssl_key = </etc/ssl/mail.example.com/privkey.pem|' /etc/dovecot/dovecot.conf

doveconf ssl_cert ssl_key
doveconf -n > /dev/null && echo "dovecot config OK"
systemctl restart dovecot
systemctl status dovecot --no-pager | grep Active

The < before each path is Dovecot syntax and tells it to read the file; Postfix paths never have it. If the second grep finds an old local_name block pointing at a missing /etc/letsencrypt/live/mail… folder, delete or comment it out, because Dovecot won’t start while a referenced file is missing.

Optional: if some clients still connect using the bare domain as the server name, add a block that serves the main domain’s own certificate for that name:

cat >> /etc/dovecot/dovecot.conf << 'EOF'

local_name example.com {
  ssl_cert = </etc/letsencrypt/live/example.com/fullchain.pem
  ssl_key = </etc/letsencrypt/live/example.com/privkey.pem
}
EOF

doveconf -n > /dev/null && systemctl restart dovecot

8. Test what each service actually serves

Files on disk only prove the files are valid. Check the live endpoints. Every result should show CN = mail.example.com:

# IMAP
openssl s_client -connect mail.example.com:993 -servername mail.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -dates

# IMAP without SNI (what a client sending no hostname receives)
echo | openssl s_client -connect 127.0.0.1:993 -noservername 2>/dev/null | openssl x509 -noout -subject

# SMTP
for p in 25 587; do
  echo "port $p"
  openssl s_client -starttls smtp -connect mail.example.com:$p -servername mail.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -dates
done
openssl s_client -connect mail.example.com:465 -servername mail.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -dates

9. Connect Outlook

  1. In Outlook, open File → Add Account.
  2. Type [email protected], then choose Advanced options → Let me set up my account manually.
  3. Choose IMAP.
  4. Enter these settings:
SettingValue
Incoming (IMAP) servermail.example.com, port 993, SSL/TLS
Outgoing (SMTP) servermail.example.com, port 587 with STARTTLS (or 465 with SSL/TLS)
Usernamethe full email address
Passwordthe one you set in CyberPanel
  • Click Connect.

The port and encryption must pair correctly: 993 with SSL/TLS, or 143 with STARTTLS. Port 143 with SSL/TLS fills the log with Too many invalid commands. Watch a connection attempt with:

tail -f /var/log/mail.log | grep -E "imap-login|auth|TLS"

A Login: user=<[email protected]> … TLS line means it worked. auth failed means a wrong username or password, not a certificate problem.

10. Confirm automatic renewal

crontab -l | grep acme
/root/.acme.sh/acme.sh --list
/root/.acme.sh/acme.sh --info -d mail.example.com --ecc | grep -iE "reload|hook"

You want a daily acme.sh cron entry, a renewal date about 60 days after issue, and Le_ReloadCmd showing the Postfix and Dovecot reload (plus the hooks if you used Option A). Let’s Encrypt certificates last 90 days.

11. Audit every certificate

for c in /etc/letsencrypt/live/*/fullchain.pem /etc/ssl/mail.example.com/fullchain.pem; do
  echo "== $c"
  openssl x509 -in "$c" -noout -subject -enddate -ext subjectAltName 2>/dev/null | grep -E "subject=|notAfter|DNS:"
done

for c in /etc/letsencrypt/live/*/fullchain.pem /etc/ssl/mail.example.com/fullchain.pem; do
  openssl x509 -in "$c" -noout -checkend 2592000 >/dev/null && echo "OK       $c" || echo "EXPIRING $c"
done

The website certificates under /etc/letsencrypt/live/ are renewed by CyberPanel itself; the mail certificate is renewed by acme.sh.

12. Finish

  • Reverse DNS: at your hosting provider, set the PTR record for the server IP to mail.example.com. Receiving servers check it.
  • Test mail: send to and from an outside address, then check tail -30 /var/log/mail.log.
  • Don’t use CyberPanel’s MailServer SSL button afterwards. It can overwrite the paths above; if you do, repeat steps 6 and 7.
  • Clean up: delete the .bak files once everything is stable.

If a service won’t start, these four commands name the cause:

doveconf -n 2>&1 | head
postfix check
journalctl -xeu dovecot.service --no-pager | tail -30
tail -40 /var/log/mail.log
Ready to Boost your Business?

Get in Touch With Us

[email protected]