This guide was created to solve TLS certificate verification issues when installing Poste.io on Dokploy, using Traefik as the reverse proxy.
The goal is to prevent conflicts between Traefik and Poste.io during certificate validation and ensure that all mail services (SMTP, IMAP, POP3) work correctly with valid TLS certificates — not just the web UI.
Before going any further, I want to be very clear: this solution is not originally mine.
All credit for the core idea and technical approach goes to @WebLenn, author of the following guide: https://gist.github.com/WebLenn/d7d74348e7be6f4fb1aa3673353390f5
After a lot of searching, his concept of “Reverse Sync” was what finally solved my issue.
This README does not attempt to take credit for the original logic. Its purpose is to:
- Document my personal implementation
- Simplify the setup
- Show how to run the sync script inside the container using Dokploy Schedules
- Avoid manual cron jobs or scripts on the VPS
My only goal is to help @WebLenn’s work reach more people who, like me, were completely stuck at this point.
When installing Poste.io using the official Dokploy template and enabling Poste.io’s internal TLS, the following error appears:
LEScript.ERROR: 400
Unable to update challenge :: authorization must be pending
Other common (and failing) attempts include:
-
Letting Dokploy / Traefik handle HTTPS
- Only the web UI gets a valid certificate
- Mail ports remain without proper TLS
-
Redirecting /.well-known/acme-challenge
- Traefik interferes with Poste.io
- Let’s Encrypt validation still fails
In short: Traefik and Poste.io compete for certificate control.
As explained by @WebLenn:
"Instead of forcing Poste.io to get its own certificate, this method "feeds" Dokploy's existing certificates into the Poste.io container."
How it works:
- Traefik obtains and renews the Let’s Encrypt certificate
- Poste.io does not request certificates
- Poste.io reads Traefik’s certificates directly
- The full certificate chain (leaf + intermediates) is preserved, which mail clients require
Using the official Poste.io template from Dokploy, simply add the Traefik certificates volume:
version: "3.8"
services:
mailserver:
image: analogic/poste.io
restart: unless-stopped
hostname: mail.${DOMAIN}
ports:
- "25:25"
- "110:110"
- "143:143"
- "465:465"
- "587:587"
- "993:993"
- "995:995"
- "4190:4190"
environment:
- TZ=${TZ}
- HTTPS=OFF
- HTTP_PORT=8080
volumes:
- /etc/dokploy/traefik/:/data/traefik # Add traefik cert route
- /etc/localtime:/etc/localtime:ro
- poste-data:/data
volumes:
poste-data: {}Make sure these variables are set correctly:
TZ=UTC
DOMAIN=domain.com
Important: DOMAIN must match with the principal domain used by Traefik to generate the certificate.
In the Poste.io service configuration create new domain:
- Domain: mail.domain.com
- HTTPS: Enabled
- Container Port: 8080
- Cert resolver: letsencrypt
If you are using the Dokploy template, remove the random domain and configure your real domain.
Once the container is running:
- Open the Poste.io admin panel
- Go to TLS Configuration
- Make sure the Let's Encrypt certificate is disabled (Poste.io must use local certificates)
In Dokploy → Poste.io container → Schedules:
- Type: SH
- Frequency: Weekly (recommended)
- Command:
IMPORTANT: You MUST set DOMAIN var to the exact domain configured
bash -c 'set -e;
ACME_FILE="/data/traefik/dynamic/acme.json";
POSTE_SSL_DIR="/data/ssl";
DOMAIN="mail.scardona.me";
echo "🔍 Checking jq...";
if ! command -v jq >/dev/null 2>&1; then
echo "⚠️ jq not found. Installing...";
if [ "$(id -u)" -ne 0 ]; then
echo "❌ jq is required but script is not running as root.";
exit 1;
fi;
apt-get update -qq && apt-get install -y jq;
echo "✅ jq installed successfully.";
else
echo "✅ jq already installed.";
fi;
mkdir -p "$POSTE_SSL_DIR";
FULL_CERT=$(jq -r ".letsencrypt.Certificates[] | select(.domain.main==\"$DOMAIN\") | .certificate" "$ACME_FILE" | base64 -d);
PRIVATE_KEY=$(jq -r ".letsencrypt.Certificates[] | select(.domain.main==\"$DOMAIN\") | .key" "$ACME_FILE" | base64 -d);
if [ -z "$FULL_CERT" ] || [ -z "$PRIVATE_KEY" ]; then
echo "❌ Certificate for $DOMAIN not found in acme.json";
exit 1;
fi;
echo "$FULL_CERT" | awk "/BEGIN CERTIFICATE/{i++}i==1" > "$POSTE_SSL_DIR/server.crt";
echo "$FULL_CERT" | awk "/BEGIN CERTIFICATE/{i++}i>1" > "$POSTE_SSL_DIR/ca.crt";
echo "$PRIVATE_KEY" > "$POSTE_SSL_DIR/server.key";
chmod 600 "$POSTE_SSL_DIR/server.crt" "$POSTE_SSL_DIR/server.key" "$POSTE_SSL_DIR/ca.crt";
dovecot reload || true;
echo "🎉 $(date): TLS cert synced and reloaded for $DOMAIN"'Run the schedule manually once and check the logs.
Expected output:
Initializing schedule
🔍 Checking jq...
⚠️ jq not found. Installing...
... Installing process [..]
✅ jq already installed.
🎉 Wed Jan 28 10:25:11 UTC 2026: TLS cert synced and reloaded for mail.scardona.me
✅ Command executed successfully
Inside the Poste.io container:
ls /data/sslYou should see:
ca.crt server.crt server.key
You can also verify the mail certificate here: https://www.checktls.com/TestReceiver
With this approach:
- Traefik and Poste.io no longer compete for Let’s Encrypt
- All configuration lives inside Dokploy
- No manual VPS configuration is required
- Certificates stay valid automatically
- Let’s Encrypt validity: 90 days
- Traefik renews around every 60 days
A clean, maintainable, and fully UI-managed solution.
Hey @scardonadev,
Thanks so much for taking my original script and adapting it to work natively inside Dokploy Schedules! That is a brilliant way to handle it, keeping everything completely within the UI without needing host-level cron jobs.
I loved your approach so much that I've actually updated my original Gist to feature your container-native method as the primary, recommended way to do it (while keeping the host-level VPS script as a secondary option for those who might still want it).
Just wanted to drop a quick note to say thanks for the collaboration and for making the solution even better for the community!
Cheers! 😄