Private Certificates with Smallstep
This guide sets up a private certificate authority (CA) using Smallstep step-ca on Ubuntu. Proxmox requests its own certificates through ACME, while Caddy manages HTTPS certificates for Sonarr, Radarr, and Prowlarr.
The architecture uses one online CA server. Its intermediate key signs service certificates; its root private key is backed up separately and removed from the online server.
All names and addresses below are examples. Replace them consistently with values for your network. Commands are intended for a fresh installation unless stated otherwise.
Architecture and example values
| Component | Example name | Example IP |
|---|---|---|
| Internal DNS | dns.lab.internal | 192.168.50.53 |
| Ubuntu CA server | ca.lab.internal | 192.168.50.10 |
| Proxmox node | pve01.lab.internal | 192.168.50.20 |
| Ubuntu application server | apps.lab.internal | 192.168.50.30 |
| Sonarr | sonarr.lab.internal | 192.168.50.30 |
| Radarr | radarr.lab.internal | 192.168.50.30 |
| Prowlarr | prowlarr.lab.internal | 192.168.50.30 |
The CA endpoint is https://ca.lab.internal:9000. These private certificates become trusted only on devices where you install the root public certificate.
The Ubuntu hostname does not need to match ca.lab.internal. The CA’s --dns setting must match the hostname clients use to connect to its HTTPS endpoint.
Prerequisites
- A dedicated Ubuntu CA server with a stable IP, reliable time synchronization, and backups.
- An Ubuntu application server with Sonarr, Radarr, and Prowlarr already running.
- A Proxmox VE node and administrative access to it.
- Internal DNS that resolves the example names to their correct addresses.
- A secure backup destination and a password manager.
Allow the following connections as appropriate for your network:
| Source | Destination | Purpose |
|---|---|---|
| Proxmox and application server | CA TCP 9000 | Certificate issuance and renewal |
| CA | Proxmox TCP 80 | ACME HTTP-01 validation |
| CA | Application server TCP 80/443 | ACME validation |
| User devices | Proxmox TCP 8006 | Proxmox HTTPS interface |
| User devices | Application server TCP 443 | Application HTTPS interfaces |
| Servers | Internal DNS UDP/TCP 53 | Name resolution |
The CA must resolve the application names itself and reach their validation ports. Public internet exposure is not needed for private ACME validation. See Smallstep’s ACME guide.
1. Check DNS and routing
On each client server, confirm that the CA resolves correctly:
| |
On the CA server, check the certificate names:
| |
Ubuntu DNS configuration
Ubuntu Server commonly uses Netplan and systemd-resolved. Inspect the current configuration before editing:
| |
Under the existing LAN interface in Netplan, configure the internal resolver:
| |
This is a fragment to merge into the existing interface configuration, not a complete Netplan file. Preserve addresses, routes, and other settings. If that interface uses DHCP and you need to reject DHCP-supplied DNS, merge this too:
| |
On a server with multiple interfaces, attach the internal DNS settings to the LAN-facing interface. A connected route to 192.168.50.0/24 already provides access to hosts on that subnet; do not add a duplicate route. Preserve any deliberate VPN/default-route arrangement.
Validate and apply with rollback protection:
| |
Confirm the change when prompted if connectivity remains working. Do not edit the generated /etc/resolv.conf stub directly. If cloud-init or another management tool owns the configuration, make the persistent change through that tool.
2. Install Smallstep on the CA server
On the CA server, enter a root shell for the installation and service configuration:
| |
Root is convenient for these commands, but the CA service will run as a dedicated unprivileged account.
Install from Smallstep’s official Ubuntu repository:
| |
Create the repository file:
| |
The final EOF must begin at the start of the line, with no leading or trailing spaces. Press Enter after it. Otherwise, the shell continues waiting for input.
| |
3. Initialize the CA
Create the service account:
| |
Create protected password files:
| |
Put a different strong password on one line in each file and save both in your password manager. The first encrypts the CA keys. The second protects the administrative provisioner key. Do not place passwords directly in shell commands.
Initialize once:
| |
bootstrap-admin is a chosen provisioner name, not a special role or automatically temporary account. Proxmox and Caddy will use separate ACME provisioners. See the initialization reference.
4. Back up and remove the online root key
Initialization creates both root and intermediate keys on this server. The root is needed for future intermediate signing operations, but not for everyday service certificate issuance.
Back up these files:
| |
The root key is encrypted using the password saved from password.txt. Store that password separately from the backup.
Copy using an SSH user
The CA directory is private to the service account. To retrieve the files through an ordinary SSH account, stage protected copies. Replace adminuser and its group/home path with your actual account:
| |
On your workstation:
| |
SCP prompts for the remote SSH user’s password if password authentication is enabled. It does not need the root key’s encryption password to copy the file.
Compare file hashes. On the CA server:
| |
On the workstation:
| |
After both hashes match and the password is saved, move the backup to secure, preferably offline storage. Then, on the CA server:
| |
Keep the root public certificate, intermediate certificate/key, and password.txt on the CA server. Removing files does not erase copies already captured in snapshots or backups. An always-connected backup machine is not offline storage.
This is a practical single-server bootstrap. For higher assurance, generate the root on an offline machine from the outset. See Smallstep’s production guidance.
5. Configure issuance policy and lifetimes
Edit the generated configuration:
| |
Inside the existing authority object, add this policy, avoiding duplicate keys:
| |
This permits names such as sonarr.lab.internal. It does not permit literal wildcard certificates, IP SANs, the bare lab.internal, or deeper names such as host.services.lab.internal. Add explicit rules if those are needed. This is an issuance policy, not a cryptographic constraint on a stolen signing key. See Smallstep policies.
In the existing authority.provisioners array, replace the generated ACME entry with these two objects. Preserve the administrative JWK provisioner:
| |
These are fragments to merge into the generated JSON. Adjacent objects require commas; the last object has no trailing comma.
This guide uses 7-day certificates for automated application services and 90-day certificates for Proxmox, giving its scheduled renewal process ample margin. Provisioner claims control these lifetimes; the provisioner names themselves do not restrict which client can use them. See Smallstep configuration.
6. Test and run under systemd
First test in the foreground:
| |
Successful startup reports that HTTPS is serving on port 9000. The command occupies the terminal because step-ca runs in the foreground. Press Ctrl+C to stop it cleanly before starting the systemd service.
Create /etc/systemd/system/step-ca.service:
| |
| |
The backslash in ExecStart explicitly continues the command. Do not split a file path across physical lines.
| |
The CA now runs in the background and starts after reboot. Verify HTTPS:
| |
Expected:
| |
Record the public root fingerprint through a trusted channel:
| |
Inspect logs with:
| |
7. Install root trust on Ubuntu and Proxmox
Distribute only root_ca.crt to clients. Never distribute the root private key for certificate trust.
From the workstation, copy the verified certificate to each target server:
| |
Use the target’s actual SSH user. On the target, inspect its fingerprint and compare it with the value recorded from the CA; ignore colon separators and letter case:
| |
Install it on Ubuntu/Debian, including Proxmox:
| |
If logged in as root on Proxmox, omit sudo. A successful health response without --cacert confirms system trust and connectivity.
8. Issue the Proxmox certificate
On the Proxmox node as root, register an ACME account. Replace the example email with a real contact address:
| |
In the Proxmox web interface:
- Select the node, then System → Certificates.
- Select the
labaccount in the ACME section. - Add a domain using the HTTP challenge.
- Enter
pve01.lab.internal. - Select Order Certificates Now.
The CA must resolve this name to the node and reach TCP 80. The management interface continues using TCP 8006:
| |
Repeat for additional nodes with their own names and certificates. Proxmox provides scheduled ACME renewal; check renewal tasks on your installed version and verify a successful renewal after deployment.
Use the supported certificate/ACME interface. Do not overwrite Proxmox’s pve-ssl.pem, pve-ssl.key, or cluster CA files. Check integrations that pin a server certificate fingerprint when certificates change. See Proxmox Certificate Management.
9. Trust the root on Omarchy / Arch Linux
On the workstation, verify the public certificate fingerprint before installing it:
| |
Fully close and reopen the browser before testing Proxmox. If command-line trust works but the browser still warns, inspect that browser’s certificate settings; some applications maintain separate trust stores.
Use DNS names in URLs. A certificate for pve01.lab.internal does not validate access by IP address.
10. Configure HTTPS for Sonarr, Radarr, and Prowlarr
The following assumes native application services on the same Ubuntu host as Caddy, default ports, and empty URL Base settings. If you already use subpath URL bases, account for those before switching to hostname-based access.
Check the application server
| |
Sonarr should listen on 8989, Radarr on 7878, and Prowlarr on 9696. Resolve any existing listener on 80/443 before installing Caddy.
Install Caddy
Use the official Caddy Ubuntu repository:
| |
| |
| |
| |
Configure the reverse proxy
Copy the already verified root certificate:
| |
For this fresh Caddy installation, replace the default configuration with:
| |
The shared snippet explicitly selects Smallstep as the ACME issuer. Caddy obtains and renews certificates and serves HTTPS, forwarding requests to the applications over local HTTP. See Caddy TLS configuration.
Validate and load:
| |
If validation succeeds:
| |
Verify all sites:
| |
An HTTP 401 Unauthorized response without a TLS error is expected when an application requires authentication. A Via: ... Caddy header confirms proxying. Open each site in a browser and sign in to verify full application access.
Keep application authentication enabled. Once you have checked integrations, restrict direct network access to application ports 8989, 7878, and 9696. Local integrations can often use loopback addresses, but review existing Prowlarr/application URLs before changing bindings or firewall rules.
11. Add other servers
For applications with native ACME support, configure:
| |
Install the root trust, create the DNS record, and allow the chosen challenge method from the CA. Generate separate private keys for separate services.
For another local HTTP application behind Caddy, add a site using the existing snippet:
| |
Replace the name and port, then validate and reload Caddy. If the backend is on another machine, consider TLS for that network connection too.
For services requiring certificate files, Smallstep offers step ca certificate and automated step ca renew. Configure renewal under a service manager and reload the consuming application after renewal. Avoid storing administrative provisioner passwords on every host. See Smallstep renewal.
Troubleshooting
| Symptom | Check or fix |
|---|---|
| Shell waits after a pasted heredoc | Enter the terminating EOF without indentation, or cancel and paste again. |
env: '--name': No such file or directory | Restore the missing step ca init command before its options. |
curl: (7) downloading packages | Check DNS filtering, firewall, routing, and endpoint reachability. It occurs before certificate verification. |
getent hosts returns nothing | Use resolvectl query, verify the DNS record, and check resolver/interface routing. |
curl: (60) against the CA | Install the verified root certificate in the client’s trust store. Do not disable TLS verification. |
| SCP cannot access the CA directory | Stage protected copies in the SSH user’s home, verify the transfer, and remove staged copies. |
step-ca occupies the terminal | Foreground execution is normal; use systemd for continuous operation. |
systemd reports Missing '=' or is a directory | Check for broken lines or split paths in ExecStart. |
| ACME validation fails | Check DNS from the CA, challenge ports, firewall rules, and CA/Caddy task logs. |
| Browser warns but curl succeeds | Restart the browser and check its trust store and the exact URL hostname. |
HTTPS returns 401 | TLS succeeded; authenticate to the application. |
If DNS filtering blocks a vendor package hostname, inspect the matching rule and allow the necessary hostname rather than leaving filtering disabled indefinitely.
Operations and recovery
- Back up CA configuration, intermediate key/certificate, unlock password, and a consistent database copy. For a simple embedded-database backup, stop the CA while copying its state and restart afterward.
- Keep the root private key backup separately protected, preferably offline. Test recovery and record where the password is stored.
- Back up Caddy configuration and persistent state, including its ACME account and certificates. Treat that state as sensitive.
- Monitor the certificates actually presented by HTTPS endpoints, as well as CA/Caddy health and renewal failures.
- Verify at least one successful automatic renewal on Proxmox and Caddy. Initial issuance alone does not prove renewal works.
- Track root and intermediate expiry and plan rotation before either expires.
- Keep time synchronized. Preserve console/SSH access for recovery if the CA VM runs on the Proxmox infrastructure it serves.
- Protect DNS administration and enrollment access. ACME name validation is not a substitute for controlling who can alter DNS or reach enrollment services.
- After key compromise, replace the key and revoke affected certificates, then remove the attacker’s enrollment access. Do not assume all clients immediately enforce revocation.
The setup is complete when the CA survives a reboot, clients validate its chain, Proxmox and all application endpoints present the expected certificates, and renewal monitoring is in place.