Skip to content

Repository files navigation

certificate-chain

One weekly renewal batch. Several certificate consumers. One deliberate WebStation restart.

Over the course of fighting comatose printers, restarting WebStations, and generally dealing with appliances that refuse to renew certificates reliably, I have worked my way toward a comprehensive and complete certificate chain.

Somewhere along the way, I ended up completely tearing Synology's Web API apart and writing my own calls to make it do what I actually wanted in the first place.

I have a habit of fixing one automation problem and accidentally discovering the next three.

Ugly_Reload began because four forced certificate renewals could finish faster than one Synology WebStation restart.

That created a race in which later certificate deployments could arrive while an earlier restart was still in progress.

Ugly_Reload removed that race. The first certificates could be installed without restarting WebStation, and the final certificate could perform one deliberate restart after the complete batch was already in place.

Problem solved.

Except Nginx Proxy Manager, Pi-hole, Plex and the router still wanted the same certificate material in different formats, in different directories and on different machines.

Apparently renewing certificates is the easy part.

Convincing the household that renewal happened requires administration.


What it does

certificate-chain.sh owns one complete weekly certificate run on Synology DSM.

It force-renews every configured wildcard certificate through acme.sh, backs up the resulting certificate directories and deploys the same known source material to every consumer.

The primary wildcard is used for Pi-hole, Plex and the router handoff. Every configured wildcard is copied to its matching Nginx Proxy Manager certificate ID.

NPM receives both the files and the correct expiry metadata. Pi-hole receives a combined PEM file. Plex receives a PKCS#12 bundle using the password already stored in Plex Preferences. The router receives three stable files through the shared Incoming directory.

Nothing is sourced from NPM merely because NPM happens to have a copy.

Consumers are consumers. They are not promoted to certificate authorities just because the files are nearby.

The complete chain

The normal all action performs the production sequence in this order:

  1. Force-renew every configured wildcard certificate in configuration order.
  2. Back up every renewed ACME certificate directory.
  3. Deploy all certificates to Nginx Proxy Manager.
  4. Deploy the primary certificate to Pi-hole.
  5. Stage the primary certificate for the router.
  6. Build and deploy the primary certificate as PKCS#12 for Plex.
  7. Remove tightly named script-created history older than the configured retention period.

Independent deployment targets all get their chance. An NPM failure is still a failure, but it does not prevent Pi-hole, the router handoff or Plex from receiving their own certificate material.

Renewal and backup remain prerequisites. If the source material is not known to be complete, deployment does not begin.

Relationship with Ugly_Reload

The forced renewal policy is intentional.

I want every consumer to receive certificate material produced by the same controlled maintenance run. A mixture of newly renewed files and older but still valid files would probably work, but "probably" is not a particularly comforting deployment strategy at 04:45.

The persisted acme.sh configuration for each certificate decides how DSM is updated during renewal.

In my batch, the early certificates use Ugly_Reload and therefore replace their DSM certificates with restart_httpd=false. The final certificate retains the normal synology_dsm deploy hook and performs the one deliberate WebStation restart after the preceding certificates are already installed.

The order in CERTIFICATES therefore matters.

certificate-chain does not replace Ugly_Reload, and Ugly_Reload does not replace certificate-chain. One prevents the DSM deployment race. The other coordinates renewal, backup and every downstream consumer around that result.

One removes the race condition.

The other stops the remaining infrastructure from improvising.

Repository files

File Runs on Purpose
certificate-chain.sh Synology DSM NAS Renew, back up, deploy, test and prune the complete certificate batch
certificate-chain.cfg.example Configuration example Documents every installation-specific value used by the NAS script
router-certificate-import.sh Synology SRM router Validate and import the staged certificate, archive it and reboot the mesh safely
mesh-health.sh Synology SRM router Report post-maintenance mesh topology, health and uptime to Log Center
nas-boot.sh Synology DSM NAS Restore the TUN device and router certificate share after a NAS reboot

The router scripts do not need a copy of certificate-chain.cfg. They consume the three files placed in Incoming and discover the current mesh through SRM's own API.

Configuration

Copy the example beside the main script:

cp certificate-chain.cfg.example certificate-chain.cfg

Then edit certificate-chain.cfg for the local installation.

The file is sourced as a trusted POSIX shell fragment. Keep it to assignments, quote values containing spaces and preserve the multiline certificate table.

Setting Purpose
ACME_HOME Existing acme.sh home containing the certificate directories and renewal recipes
ACME_SERVER Production ACME server name
ACME_STAGING_SERVER Staging ACME server used by the isolated issuance test
BACKUP_DIR Destination for certificate archives and isolated deployment tests
RETENTION_DAYS Maximum age of tightly named script-created history
NPM_SSL Nginx Proxy Manager custom certificate root
NPM_DB Nginx Proxy Manager SQLite database
NPM_CONTAINER Container restarted after a successful NPM deployment
PIHOLE_DIR Pi-hole certificate directory
PIHOLE_CONTAINER Container restarted after a successful Pi-hole deployment
ROUTER_DST Mounted router Incoming directory
PLEX_PACKAGE Synology Plex package name
PLEX_DIR Local Plex PKCS#12 output and history directory
PRIMARY_DOMAIN Wildcard shared by Pi-hole, Plex and the router
CERTIFICATES Ordered domain:NPM-ID records for the complete renewal batch

The public example uses reserved .example names. The live certificate-chain.cfg belongs beside the script on the NAS and should not be committed merely because Git is excellent at remembering regrettable things.

Requirements

The main script stays close to POSIX shell, but its job is inherently platform-aware.

Component Expected environment
Shell POSIX-style /bin/sh
Certificate client Existing acme.sh installation and renewal context
NAS tools tar, find, OpenSSL, Python 3, sqlite3 and the Docker CLI
DSM service control /usr/syno/bin/synosystemctl for Plex
NPM Direct access to its custom SSL directories and SQLite database
Pi-hole Direct access to its TLS directory and container
Plex Read access to Preferences.xml and write access to the PKCS#12 location
Router handoff Mounted Synology router Certificates/Incoming directory

The script requires root because certificate locations, container control and Synology service control are privileged operations. This is not a philosophical position on user management. It is merely Linux declining to negotiate.

First validation

After creating the live configuration, perform the checks in this order:

  1. Confirm that the configuration loads and inspect the available actions.
sudo ./certificate-chain.sh help
  1. Exercise every deployment against an isolated directory tree and a copied NPM database.
sudo ./certificate-chain.sh test
  1. Exercise renewal against Let's Encrypt staging without consuming production issuance capacity.
sudo ./certificate-chain.sh staging
  1. Run the real chain only when the configuration and isolated tests are satisfactory.
sudo ./certificate-chain.sh all

The staging action clones the existing ACME context under /tmp, removes persisted deploy, install and command hooks from the clone, renews against the staging CA and feeds the untrusted results through the isolated deployment test.

Staging certificates never enter production consumers.

That would be a surprisingly elaborate way to make every browser angry.

Actions

Action Behaviour
renew Force-renew every configured wildcard certificate
backup Archive every configured ACME certificate directory
npm Deploy all certificates to NPM and repair its expiry metadata
pihole Deploy the primary certificate to Pi-hole
plex Build and deploy the primary Plex PKCS#12 bundle
router Copy the primary leaf, key and CA to router Incoming
deploy Run NPM, Pi-hole, router and Plex deployment
prune Remove tightly named script-created history beyond retention
all Run production renewal, backup, deployment and pruning
test Test all deployment functions without changing production targets
staging Test staging issuance, backup and deployment in isolation
help Show the current interface and loaded configuration path

Several actions may be supplied in one invocation. They are processed from left to right, and the final exit status remembers if one failed.

Nginx Proxy Manager

NPM stores certificate files under fixed certificate-ID directories, but copying those files does not update the expiry date in its database.

The NPM deployment therefore copies every certificate pair through temporary names, backs up the SQLite database, derives the real expiry date from each certificate, updates the corresponding record and restarts the container only after the complete set is ready.

The metadata repair is not cosmetic. An interface confidently displaying the wrong expiry date is still wrong, even when it uses a pleasant colour scheme.

Plex

Plex expects PKCS#12 rather than loose PEM files.

The script reads customCertificateKey from Plex's existing Preferences.xml, passes it to OpenSSL through a file descriptor and never stores the password in the script or process argument list.

The previous PKCS#12 bundle is copied to old/ before replacement. Plex is then restarted through the Synology package service.

NPM is not involved in this path. Plex receives its certificate directly from the same ACME source as every other consumer.

Family trees become much easier to reason about when cousins stop pretending to be parents.

Router handoff and reboot order

certificate-chain.sh places exactly three stable files in Incoming:

cert.pem
privkey.pem
chain.pem

router-certificate-import.sh validates that all three exist, parses the leaf certificate and proves that the certificate public key matches the private key. It then backs up the active router certificate, installs the new files, restarts the router web service and moves the consumed input into a timestamped Imported directory.

Before changing the certificate, it asks SYNO.Mesh.Node.List for the live mesh topology and proves that every child has a complete path to node 0.

The reboot list is child-first. Wireless nodes are selected before eligible indirect ethernet nodes, which are selected before ethernet nodes attached directly to node 0. Each reboot signal is sent separately with a two-second pause. The main router is rebooted only after every mesh-node command has been accepted.

The order is based on the mesh that exists now, not a diagram someone happened to see in SRM two weeks ago.

Use test mode to display the exact current order without importing a certificate, restarting a service or sending a reboot command:

sudo /bin/sh /usr/syno/local/router-certificate-import.sh test

This is the safest full-mesh reboot order I can derive from the API SRM actually provides.

It is still a full-mesh reboot.

Plan accordingly.

Mesh health report

mesh-health.sh is intended to run after the maintenance window has given the network time to return.

It combines synomeshctl uptime information with the live SYNO.Mesh.Node.List response and writes one line per node directly to SRM's local RFC 5424 syslog socket.

Healthy node lines deliberately use Warning level. Synology renders them yellow, which makes the weekly report visible inside the surrounding INFO spam.

The colour is a highlighter, not a diagnosis.

Each line records the node ID, name, uptime, network state, health state, parent, link type, client count and signal value.

The old SRM BusyBox shell does not provide command -v, so the script addresses SRM's Python 2.7 installation directly as /usr/bin/python. Portability is a lovely goal, but pretending a private router API is portable would be performance art.

NAS boot recovery

nas-boot.sh handles the prerequisites that may disappear during a NAS reboot.

It recreates /dev/net/tun when needed, loads tun.ko, verifies the real CIFS mount for the router Certificates share and confirms that Incoming is available before declaring success.

The mount receives six bounded attempts because a NAS reaching the login screen does not necessarily mean its network, the router and CIFS have finished their morning coffee.

The supplied script contains installation-specific Synology paths, an internal router address and an explicit SMB 1.0 compatibility choice. Review those values before using it elsewhere.

Scheduling

Scheduling is installation policy and is not hard-coded into the scripts.

In my environment the NAS runs the complete chain during the Friday maintenance window. The router imports the staged certificate shortly afterward, and the mesh health report runs only after the network has had time to recover.

nas-boot.sh is registered as a DSM boot-up task rather than a calendar job.

The useful ordering is:

  1. Run certificate-chain.sh all on the NAS.
  2. Run router-certificate-import.sh on the router after the NAS handoff is complete.
  3. Run mesh-health.sh after the planned router and mesh reboot window.

The exact clock times belong to the installation. The dependency order does not.

Housekeeping

Let's Encrypt certificates have a short useful life, so ancient deployment history eventually becomes stale rather than reassuring.

The NAS keeps only the explicitly named archive, NPM database backup, Plex PKCS#12 backup and failed-test formats created by the script. The router applies the same principle to timestamped Backup and Imported directories.

Unexpected names are ignored. Cleanup is deliberately a narrow allowlist, not a general invitation for rm -rf to express itself creatively.

Status

This is working code from my own Synology DSM and SRM environment.

The shell is intentionally conservative because both systems ship BusyBox and private platform tools with their own opinions about modern convenience.

Read the scripts. Read the configuration example. Read the Ugly_Reload warning about Le_DeployHook before adopting that part of the batch design.

No guarantees.

No magic.

Just certificates, controlled timing, backups, validation and mild stubbornness.

License

MIT.

Use it, change it and adapt it to your own environment.

If it becomes exciting during the maintenance window, the logs are probably a better first witness than biological memory.

About

End-to-end Synology certificate automation for DSM and SRM (Appliances included)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages