AdminsForum

aaPanel FTP, SFTP & File Transfer Guides

Guide Automatically Update the Pure-FTPd FTPS Certificate in aaPanel

Started by DEV 0 replies 34 views
1DEV
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
#1
Renewing a website or mail certificate does not automatically update a separate Pure-FTPd PEM bundle. If you manually copied a certificate for FTPS, the FTP service can keep using an older certificate after the original renews. This guide closes that gap using a checked deployment helper.

What this does
The helper reads an already renewed certificate managed by aaPanel, validates it, and deploys the combined certificate/key bundle used by Pure-FTPd. It does not request a new certificate or replace the existing certificate-renewal mechanism. You must keep that original renewal working and monitor its result.

1. Confirm the source and destination
In our lab the certificate belongs to mail.adminslab.com. Its source files are:
Code:
/www/server/panel/vhost/cert/mail.adminslab.com/fullchain.pem /www/server/panel/vhost/cert/mail.adminslab.com/privkey.pem
Pure-FTPd uses
Code:
/etc/ssl/private/pure-ftpd.pem
Confirm your own hostname, certificate paths and service name first. Do not copy another site's private key or publish the key in a post. Keep the FTP bundle root-owned and readable only by root. Retain a private rollback copy and confirm provider-console access before changing the service.

2. Install the helper after reviewing it
Save the following as /usr/local/sbin/af-sync-ftps-certificate.py using your authorized root session. The code below is the lab version. Adapt the hostname, source folder and private backup folder to your server before using it. It expects Python 3, OpenSSL, ss and systemctl, and the existing pure-ftpd.service.
Code:
#!/usr/bin/python3 """Deploy an already-renewed aaPanel certificate to Pure-FTPd without exposing its key.""" import argparse,fcntl,hashlib,os,ssl,subprocess,tempfile from pathlib import Path parser=argparse.ArgumentParser();parser.add_argument('--dry-run',action='store_true');args=parser.parse_args() if os.geteuid()!=0: raise SystemExit('Root required') lock=open('/run/af-ftps-certificate-sync.lock','w');fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB) source=Path('/www/server/panel/vhost/cert/mail.adminslab.com');target=Path('/etc/ssl/private/pure-ftpd.pem') cert=source/'fullchain.pem';key=source/'privkey.pem' for p in [cert,key,target]: if not p.is_file() or p.is_symlink():raise SystemExit('Expected regular certificate file missing') subprocess.run(['openssl','x509','-in',str(cert),'-noout','-checkend','604800'],check=True,stdout=subprocess.DEVNULL) subprocess.run(['openssl','verify','-CApath','/etc/ssl/certs','-verify_hostname','mail.adminslab.com','-untrusted',str(cert),str(cert)],check=True,stdout=subprocess.DEVNULL) bundle=cert.read_bytes().rstrip()+bytes([10])+key.read_bytes().rstrip()+bytes([10]) fd,name=tempfile.mkstemp(prefix='.af-ftps-',dir=str(target.parent));tmp=Path(name) try: os.fchmod(fd,0o600) with os.fdopen(fd,'wb') as out:out.write(bundle);out.flush();os.fsync(out.fileno()) context=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER);context.load_cert_chain(str(tmp)) old=target.read_bytes() if old==bundle:print('FTPS certificate already synchronized; no restart');raise SystemExit(0) if args.dry_run:print('DRY RUN PASS: chain, hostname, expiry and matching key validated; update required');raise SystemExit(0) sessions=subprocess.check_output(['ss','-Hnt','state','established','sport','=',':21'],text=True) if sessions.strip():raise SystemExit('Active FTP control session: deferred until next run') backup=Path('/root/af-hosting-batch-20261012/ftps-renewal-20261013');backup.mkdir(mode=0o700,parents=True,exist_ok=True) previous=backup/'previous-bundle.pem';previous.write_bytes(old);previous.chmod(0o600) os.replace(tmp,target);os.chmod(target,0o600) try:   subprocess.run(['systemctl','restart','pure-ftpd.service'],check=True) except Exception:   target.write_bytes(old);target.chmod(0o600);subprocess.run(['systemctl','restart','pure-ftpd.service'],check=False);raise print('FTPS certificate deployed; Pure-FTPd restarted') finally: if tmp.exists():tmp.unlink()
Then set its ownership/permissions and run a dry check:
Code:
chown root:root /usr/local/sbin/af-sync-ftps-certificate.py chmod 700 /usr/local/sbin/af-sync-ftps-certificate.py /usr/local/sbin/af-sync-ftps-certificate.py --dry-run
The helper checks the certificate chain and hostname, a seven-day remaining-validity margin, and that the key matches the certificate. If the existing bundle already matches, it exits without restarting FTP. If a control connection is active, a live deployment is deferred. A failure message needs investigation; do not bypass the validation just to make the job complete.

3. Deploy once and verify trusted FTPS
Run the helper without --dry-run. When the source differs, it preserves the previous bundle privately, replaces the file with mode 600 and restarts Pure-FTPd. It attempts to restore the old bundle if the restart fails.
Code:
/usr/local/sbin/af-sync-ftps-certificate.py
Reconnect using the certificate's hostname and explicit FTPS. Confirm certificate validation succeeds. Do not use an IP address as the hostname when the certificate covers a DNS name. Check an encrypted upload/download after a real renewal, not just the certificate's displayed expiration date.
Actual lab deployment: updated bundle, trusted TLS 1.3, then an unchanged second run with no restart.   
Actual lab deployment: updated bundle, trusted TLS 1.3, then an unchanged second run with no restart.

4. Add the scheduled check
Our lab uses a root-owned /etc/cron.d/af-ftps-certificate-sync file with:
Code:
SHELL=/bin/sh PATH=/usr/sbin:/usr/bin:/sbin:/bin 17 * * * * root /usr/local/sbin/af-sync-ftps-certificate.py >> /var/log/af-ftps-certificate-sync.log 2>&1
This runs at minute 17 of each hour in the server's configured timezone. Use only one copy of the schedule; do not also create an identical panel task. Set the schedule file to root ownership and mode 644, and pre-create the log root-owned with mode 600. Check that the cron daemon is running. If you prefer aaPanel Cron, create the equivalent root Shell Script task and use the helper command, with private logging.

5. Monitor the complete renewal path
Read the private log for validation errors, deferred sessions or a failed restart. Inspect source certificate expiration and the actual certificate presented by FTPS. Persistent FTP sessions can delay deployment; plan a maintenance window if that continues. The old backup bundle includes a private key and must remain private. Arrange suitable log retention and an alert route outside the server.

6. Troubleshooting
  • Source missing: confirm the actual aaPanel certificate folder and that renewal produced files.
  • Chain/hostname check fails: confirm the certificate covers the FTPS hostname and includes the necessary intermediate certificates.
  • Key mismatch: use the key from the same certificate issuance; never guess or combine unrelated files.
  • Expiry margin fails: repair the original renewal process before attempting deployment.
  • Active session deferred: wait for transfers to finish; the next scheduled run can retry.
  • FTP restart fails: inspect the service configuration and restore the private previous bundle if required.

Verified scope
The lab passed dry validation, live deployment, trusted FTPS TLS 1.3, an unchanged no-restart run, and cron-service/schedule checks. A future certificate issuance and a future automatic renewal have not yet occurred, so this is not a claim that the next renewal has already succeeded. The existing aaPanel certificate-renewal job was preserved.

Start with the FTPS setup guide. Original helper and lab evidence: © AdminsForum.
Reply

Messages In This Thread
Automatically Update the Pure-FTPd FTPS Certificate in aaPanel - by DEV - 10-13-2026, 08:47 AM
Possibly Related Threads…matched by title
Replies:0Views:43
Replies:0Views:41
Replies:0Views:35

Users browsing this thread: 1 Guest(s)
Forum Jump: