Site Logo
bad ui ux

A Guide to Auto-Renew QNAP NAS TailScale Certificates

Auto-Renew QNAP NAS TailScale Let’s Encrypt Certificates

In this guide, I set up a cronjob to automatically issue and renew the TailScale TLS certificate to access my QNAP NAS via ita TailScale name with a valid TLS/SSL (https://) certificate.

Background

The TailScale connection is end-to-end encrypted, making an additional SSL certificate unnecessary. However, many services can’t distinguish between an end-to-end connection and insecure (http://) connection. For example, if you’d like to sync your contacts from your NextCloud instance with iOS, Apple rejects insecure connections1. Thus, requiring a https:// connection and thus requiring a valid SSL certificate.

Getting started

With the tailscale cert command, you can issue a Let’s Encrypt certificate that you can use for your NAS and other TailScale devices. While TailScale provides guides on how to install the certificate on a Synology NAS, they do not provide such guides for QNAP devices. The issued certificate file and private key file (.cert, .key) generated via Let’s Encrypt using the tailscale cert command need to be installed directly on the QNAP NAS. Trying to install it via the GUI (Control Center > Security > Certificate & Private Key > Replace Current Certificate) is not recommended or simply does not work:

  1. You need to manually replace the certificate at least every 90 days (Let’s Encrypt certificate ninety-day lifetime )
  2. The QNAP GUI only supports RSA keys, while Let’s Encrypt uses ECDSA for key generation. Luckily, the QNAP NAS is still able to consume ECDSA certificates under the hood, just not via the web interface.

Instead of using the manual installation guide, you can automate the whole certification renewal process.

TailScale Certificate Auto-Renew Script Setup

To install the generated certificate directly on the QNAP device, I followed the tutorial from an3k in the QNAP forum. Based on the tutorial, I created the following bash script, that you can use (with a cronjob) to automatically renew the certificates. Create a file named renew-tailscale-cert.sh at /share/CACHEDEV1_DATA/.qpkg/scripts/, containing the script. Replace the HOSTNAME with your QNAP device’s TailScale name and your Tailnet name from the TailScale admin console . Also point the TAILSCALE_PATH bash variable to the correct directory on your QNAP NAS.

The script executes the following steps:

  1. backup of the currently used certificate files
  2. issue a new certificate via the tailscale cert command2
  3. replace the current certificate with the newly generated one
  4. restart services
  5. send a success / error notification to the QNAP Notification Center
#!/bin/sh

# Variables
TAILSCALE_PATH="/share/CACHEDEV1_DATA/.qpkg/Tailscale"
CERT_DIR="/etc/stunnel"
HOSTNAME="tailname.tailXXXXXX.ts.net"
DATE=$(date +"%Y-%m-%d_%H-%M-%S")

# Backup existing cert/key if present
if [ -f "$CERT_DIR/backup.cert" ]; then
	cp "$CERT_DIR/backup.cert" "$CERT_DIR/backup_$DATE.cert"
fi

if [ -f "$CERT_DIR/backup.key" ]; then
	cp "$CERT_DIR/backup.key" "$CERT_DIR/backup_$DATE.key"
fi

if [ -f "$CERT_DIR/stunnel.pem" ]; then
	cp "$CERT_DIR/stunnel.pem" "$CERT_DIR/stunnel_$DATE.pem"
fi

# Run Tailscale cert command
if $TAILSCALE_PATH/tailscale cert \
--cert-file="$CERT_DIR/backup.cert" \
--key-file="$CERT_DIR/backup.key" \
"$HOSTNAME"; then
	cat $CERT_DIR/backup.cert $CERT_DIR/backup.key > $CERT_DIR/stunnel.pem

	/etc/init.d/thttpd.sh restart
	/etc/init.d/stunnel.sh restart
	/etc/init.d/Qthttpd.sh restart

	log_tool -a "Successfully renewed TLS certificate for $HOSTNAME" -t 0
else
	log_tool -a "FAILED to renew renewed TLS certificate for $HOSTNAME" -t 2
fi

The guide by an3k also mentions an intermediate certificate. Since this is not relevant for my setup, this is not part of this script.

Cronjob setup

To add the script to your crontab file open it via nano or vi by executing sudo crontab -e while connected via ssh to your NAS. Add the following line to your crontab file on your QNAP device. It runs the script on the first of every months at 2 a.m. and writes the log to the renew-tailscale-cert.log file.

0 2 1 * * /share/CACHEDEV1_DATA/.qpkg/scripts/renew-tailscale-cert.sh >> /var/log/renew-tailscale-cert.log 2>&1

Instead of using 0 2 1 * * you can use https://crontab.guru/#0_2_1_1_1 to construct a crontab to test the script in the next minute. After saving the entry to the crontab file, reload the crontab service with

/etc/init.d/crond.sh restart

After the script was run, you can check the log output with the command

cat /var/log/renew-tailscale-cert.log

For me, this looks similar to this:

Public cert unchanged at /etc/stunnel/backup.cert
Private key unchanged at /etc/stunnel/backup.key
Stop apache proxy: Another instance of lcdmond is already running. Exit.
Shutting down php-fpm-proxy service: OK
Shutting down thttpd service: Another instance of lcdmond is already running. Exit.
OK
Starting thttpd service: OK
Start apache proxy: OK
Shutting down apache proxy: Stop apache proxy: rm: can't remove '/var/lock/apache_proxys.pid': No such file or directory
Another instance of lcdmond is already running. Exit.
Another instance of lcdmond is already running. Exit.
Another instance of lcdmond is already running. Exit.
Shutting down php-fpm-proxy service: OK
Start apache proxy: OK
Shutting down thttpd service: OK
Shutting down Qthttpd services:Another instance of lcdmond is already running. Exit.
OK
Starting thttpd service: OK
Start apache proxy: /etc/config/php.d/php_ext.ini not found
 OK.
OK
Shutting down apache proxy: Another instance of lcdmond is already running. Exit.
Another instance of lcdmond is already running. Exit.
OK
Start apache proxy: OK
Starting Qthttpd services:Shutting down Qthttpd services:/etc/config/php.d/php_ext.ini not found
 OK.

Notice the line Public cert unchanged at /etc/stunnel/backup.cert. This means the tailscale cert command did not update your TLS certificate since it’s still valid. Within the last 30 days of the Validity Period of your certificate, Let’s Encrypt will issue a new certificate for you. Also keep in mind, that the cronjob is not perfectly configured. Instead, you’d prefer to run the script every 29 days. With the current setup it might happen that the certificate is still valid (>30 days) when the cronjob is run. If it’s a month with 31 days, there might be a day with an expired certificate before it’s renewed by the cronjob the next day. For my setup this is not a problem. Be careful to not run the script too often since you might get blocked by Let’s Encrypt for overusing their API.

Make sure to setup a notification rule to alert you per e-mail when the script run successful or failed in the QNAP Notification Center.


  1. See https://docs.nextcloud.com/server/19/user_manual/pim/sync_ios.html#calendar  ↩︎

  2. Running the command multiple times will return the same certificates while in the current certificate validity period. Keep in mind that the Let’s Encrypt endpoint has a rate limit. ↩︎