Guide to install and configure Cert Warden
Cert Warden keeps SSL certificates in one place and serves them to servers: it obtains and renews them itself via ACME (Let’s Encrypt), while clients fetch the files with API keys and restart their services. This is convenient when there are many domains and servers: one server is responsible for all certificates, and no certbot is needed on each server.
Authors and projects
Section titled “Authors and projects”Two projects this guide is built on, and the person who helped build the page itself.
How it works
Section titled “How it works”Cert Warden orders certificates from Let’s Encrypt and proves domain ownership with a DNS-01 record at the DNS provider. It stores the issued certificates and serves them to clients via API keys: the client on each server polls Cert Warden every few hours, and when there is an update it downloads the pem files, writes them to disk and restarts its web server. A panel behind Caddy needs no file certificates at all: Caddy obtains them on its own.
Installation and configuration of Cert Warden
Section titled “Installation and configuration of Cert Warden”-
Create a directory for Cert Warden and create
docker-compose.ymlroot@server: ~ mkdir /opt/certwarden && cd /opt/certwarden && nano docker-compose.ymlInsert the following content:
docker-compose.yml services:certwarden:container_name: certwardenimage: ghcr.io/gregtwallace/certwarden:latestrestart: unless-stoppedports:- '127.0.0.1:4050:4050'volumes:- ./certwarden-data:/app/datanetworks:- remnawave-networklogging:driver: 'json-file'options:max-size: '30m'max-file: '5'networks:remnawave-network:name: remnawave-networkdriver: bridgeexternal: true -
Run Cert Warden
root@server: ~ cd /opt/certwarden && docker compose up -d && docker compose logs -f -
Add Cert Warden to the panel web server so its interface is available on its own domain:
root@server: ~ cd /opt/remnawave && nano nginx.confAdd upstream section:
nginx.conf upstream certwarden {server 127.0.0.1:4050;}Add server section:
nginx.conf server {listen 443 ssl;server_name certwarden.yourdomain.com; # Don't forget to add a DNS record in Cloudflarehttp2 on;ssl_certificate "/etc/nginx/ssl/yourdomain.com/fullchain.pem";ssl_certificate_key "/etc/nginx/ssl/yourdomain.com/privkey.pem";ssl_trusted_certificate "/etc/nginx/ssl/yourdomain.com/fullchain.pem";location / {proxy_pass http://certwarden;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}}Restart nginx:
root@server: ~ cd /opt/remnawave && docker compose down remnawave-nginx && docker compose up -d remnawave-nginxroot@server: ~ cd /opt/remnawave && nano CaddyfileAdd the Cert Warden site at the end of the file:
Caddyfile certwarden.yourdomain.com {reverse_proxy 127.0.0.1:4050}Caddy obtains the certificate for this domain on its own, just like for the panel.
Restart Caddy:
root@server: ~ cd /opt/remnawave && docker compose restart remnawave-caddy -
Open your browser and go to the link
root@server: ~ https://certwarden.yourdomain.com -
Login to the system using the login admin and password password

-
We will be redirected to the main page

-
Go to the
Settingssection and change our current password
Provider Setup
Section titled “Provider Setup”-
Go to the
Providerssection -
Click on
New Provider -
Select
DNS-01 Cloudflare -
In the
Domainsfield, enter the domain for which we will be receiving wildcard certificates -
In the
API Access Methodfield, selectAPI Token. Instructions for creating a token withZone:Editpermissions can be found in the official Cloudflare documentation
-
Click on the Submit button
Private Key Setup
Section titled “Private Key Setup”-
Go to the
Private Keyssection -
Click on
New Key -
In the Name field, enter the name of the key (for example,
ACME) -
In the Key Generation Algorithm field, select
ECDSA P-384(recommended algorithm for high security and compatibility) -
Check the Disable API Key box
-
Click on the Submit button
-
A new window will pop up with our Private Key, also click on the Submit button

ACME Accounts Setup
Section titled “ACME Accounts Setup”-
Go to the
ACME Accountssection -
Click on
New Account -
In the Name field, enter the name of our domain or your own other name
-
In the Contact E-mail Address field, enter the email to which notifications will be sent
-
In the ACME Server field, select
Let’s Encrypt -
In the Private Key field, select your previously created key
-
Check the Accept ACME Server`s Terms of Service box
-
Click on the Submit button
-
A window with account data will pop up

-
Click on the REGISTER button and the Account Status should become Valid
-
Click on the Submit button
Certificates Setup
Section titled “Certificates Setup”-
Go to the
Certificatessection -
Click on
New Certificate -
In the Name field, enter the name of our domain or your own other name
-
In the ACME Account field, select the previously created account
-
In the
Subject (and Common Name)field, enter the domain 2 levels, for exampleexample.com -
Click on the
ADDbutton and in theAlternate Name 1field enter the already wildcard domain*.example.com -
Click on the Submit button

-
A window with ACME Orders will pop up

-
Click on the
PLACE NEW ORDERbutton -
Now we wait for the certificate to be received, we can track the status in the
ACME Queuesection, as soon as the domain disappears, the certificate is received -
Go to the
Dashboardsection and see that the certificate is received and how many days are left until the expiration date
Cert Warden Client
Section titled “Cert Warden Client”The Cert Warden client runs as a Docker container: it fetches certificates from the server using API keys, saves the files to disk and restarts the specified containers so the services pick up the update.
The guide covers both versions of the client. The official client is the primary option here: the server pushes the certificate to the client, so the client keeps a port open and decrypts the payload with an AES key. The alternative, the mishkatik/certwarden-client fork, flips the scheme: the client polls the server itself, opens no ports and needs no AES key.
How the fork behaves:
Client on the Remnawave panel server
Section titled “Client on the Remnawave panel server”-
Create a directory for the client:
root@server: ~ mkdir -p /opt/certwardenclient/certs && cd /opt/certwardenclient && nano docker-compose.ymlInsert the following content:
docker-compose.yml services:certwardenclient:image: ghcr.io/gregtwallace/certwarden-client:latestcontainer_name: certwardenclientrestart: unless-stoppedports:- '127.0.0.1:5055:5055'volumes:- /var/run/docker.sock:/var/run/docker.sock- /opt/certwardenclient/certs:/opt/certwarden/certsenvironment:TZ: "Europe/Moscow"CW_CLIENT_FILE_UPDATE_TIME_START: "02:00"CW_CLIENT_FILE_UPDATE_TIME_END: "03:00"CW_CLIENT_FILE_UPDATE_DAYS_OF_WEEK: "Mon Tue Wed Thu Fri Sat Sun"CW_CLIENT_RESTART_DOCKER_CONTAINER0: "remnawave-nginx"CW_CLIENT_AES_KEY_BASE64: "pLZEc_QvSka8syfQtuTfArblalMKU6C6Ke1uIji0E5g"CW_CLIENT_SERVER_ADDRESS: "https://certwarden.example.com"CW_CLIENT_KEY_NAME: "example.com"CW_CLIENT_KEY_APIKEY: "0UkmZEZK7oieQ6hmz9Fta2obMMZRjAuo"CW_CLIENT_CERT_NAME: "example.com"CW_CLIENT_CERT_APIKEY: "MkYMf7eXO8aB9xiNKqwvNVcDGKJ0Dg6v"CW_CLIENT_CERT_PATH: "/opt/certwarden/certs"CW_CLIENT_KEY_PEM_FILENAME: "privkey.pem"CW_CLIENT_CERTCHAIN_PEM_FILENAME: "fullchain.pem"networks:- remnawave-networklogging:driver: 'json-file'options:max-size: '30m'max-file: '5'networks:remnawave-network:name: remnawave-networkdriver: bridgeexternal: trueCW_CLIENT_RESTART_DOCKER_CONTAINER0is set toremnawave-nginx. For a panel behind Caddy, useremnawave-caddy. -
CW_CLIENT_AES_KEY_BASE64is the key from theCertificates→Post Processing→Client AES Keysection. ClickGENERATEto obtain it.
-
CW_CLIENT_SERVER_ADDRESSis the address of the Cert Warden server, for examplehttps://certwarden.example.com. -
CW_CLIENT_KEY_NAMEandCW_CLIENT_KEY_APIKEYare the name and the API key of the private key from thePrivate Keyssection. -
CW_CLIENT_CERT_NAMEandCW_CLIENT_CERT_APIKEYare the name and the API key of the certificate from theCertificatessection. -
Run the client:
root@server: ~ cd /opt/certwardenclient && docker compose up -d && docker compose logs -f -
The
fullchain.pemandprivkey.pemfiles should appear in the/opt/certwardenclient/certsfolder. -
Mount them in the panel’s
docker-compose.yml:root@server: ~ cd /opt/remnawave && nano docker-compose.ymlAdd to volumes, do not forget to specify your domain:
docker-compose.yml volumes:- /opt/certwardenclient/certs/fullchain.pem:/etc/nginx/ssl/yourdomain.com/fullchain.pem- /opt/certwardenclient/certs/privkey.pem:/etc/nginx/ssl/yourdomain.com/privkey.pem -
Restart nginx:
root@server: ~ cd /opt/remnawave && docker compose down remnawave-nginx && docker compose up -d remnawave-nginx
-
Check the certificate settings in Cert Warden: under
Certificates→Post Processing→Client, the client address and theClient AES Keymust be empty. The client fetches the certificate itself, there is nowhere to send it. If anything is filled in there, the server logs an error on every renewal. -
Create a directory for the client:
root@server: ~ mkdir -p /opt/certwardenclient/certs && cd /opt/certwardenclient && nano docker-compose.ymlInsert the following content:
docker-compose.yml services:certwardenclient:image: ghcr.io/mishkatik/certwarden-client:latestcontainer_name: certwardenclientrestart: unless-stoppedvolumes:- /var/run/docker.sock:/var/run/docker.sock- /opt/certwardenclient/certs:/opt/certwarden/certsenvironment:TZ: "Europe/Moscow"CW_CLIENT_SERVER_ADDRESS: "https://certwarden.example.com"CW_CLIENT_POLL_INTERVAL: "6h"CW_CLIENT_0_CERT_NAME: "example.com"CW_CLIENT_0_CERT_APIKEY: "MkYMf7eXO8aB9xiNKqwvNVcDGKJ0Dg6v"CW_CLIENT_0_KEY_APIKEY: "0UkmZEZK7oieQ6hmz9Fta2obMMZRjAuo"CW_CLIENT_0_FILE_UPDATE_TIME_START: "02:00"CW_CLIENT_0_FILE_UPDATE_TIME_END: "03:00"CW_CLIENT_0_FILE_UPDATE_DAYS_OF_WEEK: "Mon Tue Wed Thu Fri Sat Sun"CW_CLIENT_0_RESTART_DOCKER_CONTAINER0: "remnawave-nginx"CW_CLIENT_0_CERT_PATH: "/opt/certwarden/certs"CW_CLIENT_0_KEY_PEM_FILENAME: "privkey.pem"CW_CLIENT_0_CERTCHAIN_PEM_FILENAME: "fullchain.pem"logging:driver: 'json-file'options:max-size: '30m'max-file: '5'CW_CLIENT_0_RESTART_DOCKER_CONTAINER0is set toremnawave-nginx. For a panel behind Caddy, useremnawave-caddy. -
CW_CLIENT_SERVER_ADDRESSis the address of the Cert Warden server, for examplehttps://certwarden.example.com. It must start withhttps://, otherwise the client does not start. -
CW_CLIENT_POLL_INTERVALsets how often to poll the server:30m,6h,12hand so on, but no more often than once a minute. The server logs every request, so a very short interval floods its log. -
CW_CLIENT_0_CERT_NAMEandCW_CLIENT_0_CERT_APIKEYare the name and the API key of the certificate from theCertificatessection. -
CW_CLIENT_0_KEY_APIKEYis the API key of the private key the certificate uses (thePrivate Keyssection). If you switch the certificate to another key on the server, update this variable and restart the client. -
CW_CLIENT_0_FILE_UPDATE_TIME_START,CW_CLIENT_0_FILE_UPDATE_TIME_ENDandCW_CLIENT_0_FILE_UPDATE_DAYS_OF_WEEKset the window in which the client writes a renewed certificate and restarts the containers. The time follows theTZtime zone, or UTC without it. -
Run the client:
root@server: ~ cd /opt/certwardenclient && docker compose up -d && docker compose logs -f -
The
fullchain.pemandprivkey.pemfiles should appear in the/opt/certwardenclient/certsfolder. -
Mount them in the panel’s
docker-compose.yml:root@server: ~ cd /opt/remnawave && nano docker-compose.ymlAdd to volumes, do not forget to specify your domain:
docker-compose.yml volumes:- /opt/certwardenclient/certs/fullchain.pem:/etc/nginx/ssl/yourdomain.com/fullchain.pem- /opt/certwardenclient/certs/privkey.pem:/etc/nginx/ssl/yourdomain.com/privkey.pem -
Restart nginx:
root@server: ~ cd /opt/remnawave && docker compose down remnawave-nginx && docker compose up -d remnawave-nginx
Client on the Remnawave node server
Section titled “Client on the Remnawave node server”-
Create a directory for the client:
root@server: ~ mkdir -p /opt/certwardenclient/certs && cd /opt/certwardenclient && nano docker-compose.ymlInsert the following content:
docker-compose.yml services:certwardenclient:image: ghcr.io/gregtwallace/certwarden-client:latestcontainer_name: certwardenclientrestart: unless-stoppedports:- '127.0.0.1:5055:5055'volumes:- /var/run/docker.sock:/var/run/docker.sock- /opt/certwardenclient/certs:/opt/certwarden/certsenvironment:TZ: "Europe/Moscow"CW_CLIENT_FILE_UPDATE_TIME_START: "02:00"CW_CLIENT_FILE_UPDATE_TIME_END: "03:00"CW_CLIENT_FILE_UPDATE_DAYS_OF_WEEK: "Mon Tue Wed Thu Fri Sat Sun"CW_CLIENT_RESTART_DOCKER_CONTAINER0: "remnawave-nginx"CW_CLIENT_AES_KEY_BASE64: "pLZEc_QvSka8syfQtuTfArblalMKU6C6Ke1uIji0E5g"CW_CLIENT_SERVER_ADDRESS: "https://certwarden.example.com"CW_CLIENT_KEY_NAME: "example.com"CW_CLIENT_KEY_APIKEY: "0UkmZEZK7oieQ6hmz9Fta2obMMZRjAuo"CW_CLIENT_CERT_NAME: "example.com"CW_CLIENT_CERT_APIKEY: "MkYMf7eXO8aB9xiNKqwvNVcDGKJ0Dg6v"CW_CLIENT_CERT_PATH: "/opt/certwarden/certs"CW_CLIENT_KEY_PEM_FILENAME: "privkey.pem"CW_CLIENT_CERTCHAIN_PEM_FILENAME: "fullchain.pem"logging:driver: 'json-file'options:max-size: '30m'max-file: '5' -
CW_CLIENT_AES_KEY_BASE64is the key from theCertificates→Post Processing→Client AES Keysection. ClickGENERATEto obtain it. -
CW_CLIENT_SERVER_ADDRESSis the address of the Cert Warden server. -
CW_CLIENT_KEY_NAMEandCW_CLIENT_KEY_APIKEYare the name and the API key from thePrivate Keyssection. -
CW_CLIENT_CERT_NAMEandCW_CLIENT_CERT_APIKEYare the name and the API key from theCertificatessection. -
Run the client:
root@server: ~ cd /opt/certwardenclient && docker compose up -d && docker compose logs -f -
The
fullchain.pemandprivkey.pemfiles should appear in the/opt/certwardenclient/certsfolder. -
Mount them in the node’s
docker-compose.yml(its web server is also calledremnawave-nginx):root@server: ~ cd /opt/remnanode && nano docker-compose.ymldocker-compose.yml volumes:- /opt/certwardenclient/certs/fullchain.pem:/etc/nginx/ssl/yourdomain.com/fullchain.pem- /opt/certwardenclient/certs/privkey.pem:/etc/nginx/ssl/yourdomain.com/privkey.pem -
Restart the node’s nginx:
root@server: ~ cd /opt/remnanode && docker compose down remnawave-nginx && docker compose up -d remnawave-nginx
-
Create a directory for the client:
root@server: ~ mkdir -p /opt/certwardenclient/certs && cd /opt/certwardenclient && nano docker-compose.ymlInsert the following content:
docker-compose.yml services:certwardenclient:image: ghcr.io/mishkatik/certwarden-client:latestcontainer_name: certwardenclientrestart: unless-stoppedvolumes:- /var/run/docker.sock:/var/run/docker.sock- /opt/certwardenclient/certs:/opt/certwarden/certsenvironment:TZ: "Europe/Moscow"CW_CLIENT_SERVER_ADDRESS: "https://certwarden.example.com"CW_CLIENT_POLL_INTERVAL: "6h"CW_CLIENT_0_CERT_NAME: "example.com"CW_CLIENT_0_CERT_APIKEY: "MkYMf7eXO8aB9xiNKqwvNVcDGKJ0Dg6v"CW_CLIENT_0_KEY_APIKEY: "0UkmZEZK7oieQ6hmz9Fta2obMMZRjAuo"CW_CLIENT_0_FILE_UPDATE_TIME_START: "02:00"CW_CLIENT_0_FILE_UPDATE_TIME_END: "03:00"CW_CLIENT_0_FILE_UPDATE_DAYS_OF_WEEK: "Mon Tue Wed Thu Fri Sat Sun"CW_CLIENT_0_RESTART_DOCKER_CONTAINER0: "remnawave-nginx"CW_CLIENT_0_CERT_PATH: "/opt/certwarden/certs"CW_CLIENT_0_KEY_PEM_FILENAME: "privkey.pem"CW_CLIENT_0_CERTCHAIN_PEM_FILENAME: "fullchain.pem"logging:driver: 'json-file'options:max-size: '30m'max-file: '5' -
Fill in the variables the same way as on the panel server:
CW_CLIENT_SERVER_ADDRESSis the address of the Cert Warden server,CW_CLIENT_0_CERT_NAMEandCW_CLIENT_0_CERT_APIKEYcome from theCertificatessection,CW_CLIENT_0_KEY_APIKEYfrom thePrivate Keyssection. If the node receives a different certificate, itsPost Processing→Clientmust be empty too. -
Run the client:
root@server: ~ cd /opt/certwardenclient && docker compose up -d && docker compose logs -f -
The
fullchain.pemandprivkey.pemfiles should appear in the/opt/certwardenclient/certsfolder. -
Mount them in the node’s
docker-compose.yml(its web server is also calledremnawave-nginx):root@server: ~ cd /opt/remnanode && nano docker-compose.ymldocker-compose.yml volumes:- /opt/certwardenclient/certs/fullchain.pem:/etc/nginx/ssl/yourdomain.com/fullchain.pem- /opt/certwardenclient/certs/privkey.pem:/etc/nginx/ssl/yourdomain.com/privkey.pem -
Restart the node’s nginx:
root@server: ~ cd /opt/remnanode && docker compose down remnawave-nginx && docker compose up -d remnawave-nginx
Closing access to Certwarden with Netbird
Section titled “Closing access to Certwarden with Netbird”-
Add the Cert Warden server to Netbird, as its installation can be seen here
-
In the Cloudflare control panel for your domain certwarden.yourdomain.com, specify the IP address of the Cert Warden server in the Netbird network

-
Now, to access the Cert Warden page, you will need to connect to your network on your computer through the Netbird application
