Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# UniFi OS Server SSL Import Script

A script to automatically import and update SSL certificates for your UniFi OS server (`uosserver`) from multiple certificate providers. The script supports both **Let's Encrypt via Certbot** and **acme.sh**, with DNS challenge support for all out-of-the-box DNS providers. The script stops the UniFi controller, replaces its TLS key and certificate files with the latest certs, sets correct permissions, and then restarts the controller — ensuring your UniFi server always has a valid SSL certificate.
A script to automatically import and update SSL certificates for your UniFi OS server (`uosserver`) from multiple certificate providers. The script supports **Let's Encrypt via Certbot**, **acme.sh**, and **Caddy**, with DNS challenge support for all out-of-the-box DNS providers. The script stops the UniFi controller, replaces its TLS key and certificate files with the latest certs, sets correct permissions, and then restarts the controller — ensuring your UniFi server always has a valid SSL certificate.

---

## Features

- **Multiple Certificate Providers**: Support for both Certbot and acme.sh
- **Multiple Certificate Providers**: Support for Certbot, acme.sh, and Caddy
- **DNS Challenge Support**: Works with Cloudflare and all supported acme.sh DNS providers
- **Smart Updates**: Automatically detects if the certificate changed and updates only if needed
- **Command Line Options**:
- `--force` to reinstall certificate even if unchanged
- `--verbose` for detailed logs and command output
- `--provider=certbot|acme` to specify certificate provider
- `--provider=certbot|acme|caddy` to specify certificate provider
- `--dns=cloudflare` to specify DNS provider (for acme.sh)
- **Safety Features**: Creates backups of existing key and cert files before updating
- **Comprehensive Logging**: Logs actions to `/var/log/unifi-ssl-import.log`
Expand Down Expand Up @@ -107,6 +107,32 @@ acme.sh --issue --dns dns_hetzner -d your.domain.com --keylength 4096

**Important**: UniFi OS Server only supports RSA certificates, so always use `--keylength 2048` (or higher RSA key lengths) with acme.sh. ECC certificates are not supported.

### Option 3: Caddy Server

If you're using Caddy as your reverse proxy, it automatically generates and manages Let's Encrypt certificates. This option allows you to import existing certificates that Caddy has already generated, without needing to generate new ones.

#### Prerequisites

- Caddy server must be installed and configured
- Caddy must have already generated certificates for your domain
- Certificates should be located at: `/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/your.domain.com/`

#### Caddy Configuration Example

Your Caddyfile should include your UniFi domain:

```caddyfile
your.domain.com {
reverse_proxy localhost:443 {
transport http {
tls_insecure_skip_verify
}
}
}
```

After Caddy generates certificates, you can use this script to import them into UniFi OS.

---

## Install the Import Script
Expand Down Expand Up @@ -135,7 +161,7 @@ Look for and modify the following configuration variables:
# Domain Name:
UNIFI_HOSTNAME="unifi.example.com"

# Certificate Provider: "certbot" or "acme"
# Certificate Provider: "certbot", "acme", or "caddy"
CERT_PROVIDER="certbot"

# DNS Provider (for acme.sh): "cloudflare", "hetzner", etc.
Expand Down Expand Up @@ -168,6 +194,9 @@ You can override the configuration using command line arguments:
# Use acme.sh with Cloudflare DNS
/usr/local/bin/unifi-osserver-ssl-import.sh --provider=acme --dns=cloudflare

# Use Caddy certificates
/usr/local/bin/unifi-osserver-ssl-import.sh --provider=caddy

# Force certificate reinstallation
/usr/local/bin/unifi-osserver-ssl-import.sh --force

Expand All @@ -179,7 +208,7 @@ You can override the configuration using command line arguments:
```

### Available Options:
- `--provider=certbot|acme` – Specify certificate provider
- `--provider=certbot|acme|caddy` – Specify certificate provider
- `--dns=cloudflare|hetzner` – Specify DNS provider (for acme.sh only)
- `--verbose` – Show detailed output of what the script is doing
- `--force` – Force reimport of certificate even if it hasn't changed
Expand Down Expand Up @@ -218,6 +247,15 @@ acme.sh automatically installs its own cron job for renewal. You only need to ad
5 */12 * * * root /usr/local/bin/unifi-osserver-ssl-import.sh --provider=acme --dns=hetzner >> /home/import_log.txt 2>&1
```

### For Caddy users:

Caddy automatically renews certificates. You only need to add the import script:

```cron
# Check for certificate updates twice a day
5 */12 * * * root /usr/local/bin/unifi-osserver-ssl-import.sh --provider=caddy >> /home/import_log.txt 2>&1
```

### Provider-agnostic approach:

You can also set the provider in the script configuration and use:
Expand Down
43 changes: 39 additions & 4 deletions unifi-osserver-ssl-import
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#Domain Name:
UNIFI_HOSTNAME="unifi.example.com"

#Certificate Provider: "certbot" or "acme"
#Certificate Provider: "certbot", "acme", or "caddy"
CERT_PROVIDER="certbot"

#DNS Provider (for acme.sh): "cloudflare", "hetzner", etc.
Expand All @@ -14,6 +14,7 @@ DNS_PROVIDER="cloudflare"
#Configuration paths:
ACME_HOME="/root/.acme.sh"
CERTBOT_CONFIG_DIR="/etc/letsencrypt"
CADDY_DATA_DIR="/var/lib/caddy/.local/share/caddy"

#Force Mode:
FORCE=false
Expand All @@ -34,7 +35,7 @@ for arg in "$@"; do
--dns=*) DNS_PROVIDER="${arg#*=}" ;;
*)
echo "Unknown argument: $arg"
echo "Usage: $0 [--force] [--verbose] [--provider=certbot|acme] [--dns=cloudflare|hetzner]"
echo "Usage: $0 [--force] [--verbose] [--provider=certbot|acme|caddy] [--dns=cloudflare|hetzner]"
exit 1
;;
esac
Expand Down Expand Up @@ -75,6 +76,19 @@ get_cert_dir() {
exit 1
fi
;;
"caddy")
# Caddy stores certificates in subdirectories by ACME server and domain
local caddy_cert_dir="${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/${UNIFI_HOSTNAME}"

if [[ -d "$caddy_cert_dir" ]]; then
echo "$caddy_cert_dir"
else
echo "❌ No Caddy certificate directory found for ${UNIFI_HOSTNAME}" >&2
echo " Expected directory: $caddy_cert_dir" >&2
echo " Make sure Caddy has generated certificates for this domain" >&2
exit 1
fi
;;
*)
echo "❌ Unknown certificate provider: $CERT_PROVIDER" >&2
exit 1
Expand Down Expand Up @@ -106,6 +120,13 @@ get_cert_files() {
exit 1
fi
;;
"caddy")
# Caddy stores certificates as domain.crt and domain.key
CERT_FILE="${cert_dir}/${UNIFI_HOSTNAME}.crt"
KEY_FILE="${cert_dir}/${UNIFI_HOSTNAME}.key"
# Caddy's .crt file already contains the full certificate chain
CHAIN_FILE=""
;;
esac
}

Expand All @@ -125,9 +146,16 @@ validate_provider_config() {
exit 1
fi
;;
"caddy")
if [[ ! -d "$CADDY_DATA_DIR" ]]; then
echo "❌ Caddy data directory not found: $CADDY_DATA_DIR"
echo " Please make sure Caddy is installed and has generated certificates"
exit 1
fi
;;
*)
echo "❌ Unsupported certificate provider: $CERT_PROVIDER"
echo " Supported providers: certbot, acme"
echo " Supported providers: certbot, acme, caddy"
exit 1
;;
esac
Expand Down Expand Up @@ -168,7 +196,13 @@ if [[ "$CERT_PROVIDER" == "certbot" ]]; then
echo "❌ Missing certificate files. Aborting."
exit 1
fi
elif [[ "$CERT_PROVIDER" == "caddy" ]]; then
if [[ ! -f "$CERT_FILE" ]]; then
echo "❌ Missing certificate file: $CERT_FILE. Aborting."
exit 1
fi
else
# acme.sh provider
if [[ ! -f "$CERT_FILE" ]]; then
echo "❌ Missing certificate file: $CERT_FILE. Aborting."
exit 1
Expand All @@ -180,6 +214,7 @@ echo "✅ All required files found."
if [[ "$CERT_PROVIDER" == "certbot" ]]; then
CURRENT_SUM=$(cat "$KEY_FILE" "$CERT_FILE" "$CHAIN_FILE" | md5sum | awk '{print $1}')
else
# For acme.sh and caddy, only use key and cert files
CURRENT_SUM=$(cat "$KEY_FILE" "$CERT_FILE" | md5sum | awk '{print $1}')
fi
echo "Current cert bundle MD5: $CURRENT_SUM"
Expand Down Expand Up @@ -229,7 +264,7 @@ if [[ "$CERT_PROVIDER" == "certbot" ]]; then
cat "$CERT_FILE" "$CHAIN_FILE" > "$DEST_CERT" 2>/dev/null
fi
else
# For acme.sh, fullchain.cer already contains both cert and chain
# For acme.sh and caddy, the cert file already contains the full chain
run_command cp -v "$CERT_FILE" "$DEST_CERT"
fi
echo "✅"
Expand Down