Skip to content

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.

Where we install

We will deploy it on the Remnawave panel server. Any other server works too.

Domain validation

The DNS-01 method: Cloudflare has its own provider, Gcore and Bunny.net connect through lego.

Serving clients

The Cert Warden client on each server downloads certificates and restarts the services.

Two projects this guide is built on, and the person who helped build the page itself.

Cert Warden

The certificate server of this guide: obtains and renews certificates via ACME, proves domains with DNS-01 and serves them to clients by API keys.

Client fork

The client from the “Fork” tab: polls the server on a schedule, opens no ports and needs no AES key.

Guide help

This Cert Warden guide is co-authored: the structure, the steps and the wording of this page were worked out together with maposia.

Roleguide co-authorHelpedshape the structure, steps and text
maposia
maposia@maposia
Let’s Encryptissues and renews via ACMEDNS providerCloudflare · Gcore · BunnycertificateDNS-01 · TXTCert Warden:4050 · stores and serves via API keysserved via API keysclient · panelpem files to diskrestarts nginx / caddyclient · de-fra-01pem files to diskrestarts nginxclient · nl-ams-02pem files to diskrestarts nginx

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”
  1. Create a directory for Cert Warden and create docker-compose.yml

    root@server: ~
    mkdir /opt/certwarden && cd /opt/certwarden && nano docker-compose.yml

    Insert the following content:

    docker-compose.yml
    services:
    certwarden:
    container_name: certwarden
    image: ghcr.io/gregtwallace/certwarden:latest
    restart: unless-stopped
    ports:
    - '127.0.0.1:4050:4050'
    volumes:
    - ./certwarden-data:/app/data
    networks:
    - remnawave-network
    logging:
    driver: 'json-file'
    options:
    max-size: '30m'
    max-file: '5'
    networks:
    remnawave-network:
    name: remnawave-network
    driver: bridge
    external: true
  2. Run Cert Warden

    root@server: ~
    cd /opt/certwarden && docker compose up -d && docker compose logs -f
  3. Add Cert Warden to the panel web server so its interface is available on its own domain:

    root@server: ~
    cd /opt/remnawave && nano nginx.conf

    Add 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 Cloudflare
    http2 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-nginx
  4. Open your browser and go to the link

    root@server: ~
    https://certwarden.yourdomain.com
  5. Login to the system using the login admin and password password

    Cert Warden login
  6. We will be redirected to the main page

    Cert Warden dashboard

  7. Go to the Settings section and change our current password

  1. Go to the Providers section

  2. Click on New Provider

  3. Select DNS-01 Cloudflare

  4. In the Domains field, enter the domain for which we will be receiving wildcard certificates

  5. In the API Access Method field, select API Token. Instructions for creating a token with Zone:Edit permissions can be found in the official Cloudflare documentation

    Cert Warden New Challenge Provider
  6. Click on the Submit button

  1. Go to the Private Keys section

  2. Click on New Key

  3. In the Name field, enter the name of the key (for example, ACME)

  4. In the Key Generation Algorithm field, select ECDSA P-384 (recommended algorithm for high security and compatibility)

  5. Check the Disable API Key box

  6. Click on the Submit button

  7. A new window will pop up with our Private Key, also click on the Submit button

    Cert Warden New Private Key
  1. Go to the ACME Accounts section

  2. Click on New Account

  3. In the Name field, enter the name of our domain or your own other name

  4. In the Contact E-mail Address field, enter the email to which notifications will be sent

  5. In the ACME Server field, select Let’s Encrypt

  6. In the Private Key field, select your previously created key

  7. Check the Accept ACME Server`s Terms of Service box

  8. Click on the Submit button

  9. A window with account data will pop up

    Cert Warden New ACME Account
  10. Click on the REGISTER button and the Account Status should become Valid

  11. Click on the Submit button

  1. Go to the Certificates section

  2. Click on New Certificate

  3. In the Name field, enter the name of our domain or your own other name

  4. In the ACME Account field, select the previously created account

  5. In the Subject (and Common Name) field, enter the domain 2 levels, for example example.com

  6. Click on the ADD button and in the Alternate Name 1 field enter the already wildcard domain *.example.com

  7. Click on the Submit button

    Cert Warden New Certificate
  8. A window with ACME Orders will pop up

    Cert Warden ACME Orders

  9. Click on the PLACE NEW ORDER button

  10. Now we wait for the certificate to be received, we can track the status in the ACME Queue section, as soon as the domain disappears, the certificate is received

  11. Go to the Dashboard section and see that the certificate is received and how many days are left until the expiration date

    Cert Warden Dashboard

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:

Polling on a schedule

At startup and then once per configured interval the client makes a single request to Cert Warden. If the certificate has not changed, the server answers with an empty 304 and nothing is downloaded.

IntervalCW_CLIENT_POLL_INTERVAL, 6 hours by default

First start

On the first start the client does not wait for the renewal window: it downloads the certificate right away, writes the files and restarts the containers.

Filesfullchain.pem and privkey.pem

Renewal window

A renewed certificate is written within the configured time window, 02:00 to 03:00 in the example below, and the containers are restarted afterwards.

WindowFILE_UPDATE_TIME_START / FILE_UPDATE_TIME_ENDDaysFILE_UPDATE_DAYS_OF_WEEKTime zonefrom the TZ variable, UTC without it

Retry on failure

If the server is unreachable or an API key is wrong, the client keeps running: it logs an error and retries.

First pause15 minutesBackoffthe pause doubles after each failureLimitthe poll interval
  1. Create a directory for the client:

    root@server: ~
    mkdir -p /opt/certwardenclient/certs && cd /opt/certwardenclient && nano docker-compose.yml

    Insert the following content:

    docker-compose.yml
    services:
    certwardenclient:
    image: ghcr.io/gregtwallace/certwarden-client:latest
    container_name: certwardenclient
    restart: unless-stopped
    ports:
    - '127.0.0.1:5055:5055'
    volumes:
    - /var/run/docker.sock:/var/run/docker.sock
    - /opt/certwardenclient/certs:/opt/certwarden/certs
    environment:
    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-network
    logging:
    driver: 'json-file'
    options:
    max-size: '30m'
    max-file: '5'
    networks:
    remnawave-network:
    name: remnawave-network
    driver: bridge
    external: true

    CW_CLIENT_RESTART_DOCKER_CONTAINER0 is set to remnawave-nginx. For a panel behind Caddy, use remnawave-caddy.

  2. CW_CLIENT_AES_KEY_BASE64 is the key from the Certificates → Post Processing → Client AES Key section. Click GENERATE to obtain it.

    Cert Warden AES Key
  3. CW_CLIENT_SERVER_ADDRESS is the address of the Cert Warden server, for example https://certwarden.example.com.

  4. CW_CLIENT_KEY_NAME and CW_CLIENT_KEY_APIKEY are the name and the API key of the private key from the Private Keys section.

  5. CW_CLIENT_CERT_NAME and CW_CLIENT_CERT_APIKEY are the name and the API key of the certificate from the Certificates section.

  6. Run the client:

    root@server: ~
    cd /opt/certwardenclient && docker compose up -d && docker compose logs -f
  7. The fullchain.pem and privkey.pem files should appear in the /opt/certwardenclient/certs folder.

  8. Mount them in the panel’s docker-compose.yml:

    root@server: ~
    cd /opt/remnawave && nano docker-compose.yml

    Add 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
  9. Restart nginx:

    root@server: ~
    cd /opt/remnawave && docker compose down remnawave-nginx && docker compose up -d remnawave-nginx
  1. Create a directory for the client:

    root@server: ~
    mkdir -p /opt/certwardenclient/certs && cd /opt/certwardenclient && nano docker-compose.yml

    Insert the following content:

    docker-compose.yml
    services:
    certwardenclient:
    image: ghcr.io/gregtwallace/certwarden-client:latest
    container_name: certwardenclient
    restart: unless-stopped
    ports:
    - '127.0.0.1:5055:5055'
    volumes:
    - /var/run/docker.sock:/var/run/docker.sock
    - /opt/certwardenclient/certs:/opt/certwarden/certs
    environment:
    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'
  2. CW_CLIENT_AES_KEY_BASE64 is the key from the Certificates → Post Processing → Client AES Key section. Click GENERATE to obtain it.

  3. CW_CLIENT_SERVER_ADDRESS is the address of the Cert Warden server.

  4. CW_CLIENT_KEY_NAME and CW_CLIENT_KEY_APIKEY are the name and the API key from the Private Keys section.

  5. CW_CLIENT_CERT_NAME and CW_CLIENT_CERT_APIKEY are the name and the API key from the Certificates section.

  6. Run the client:

    root@server: ~
    cd /opt/certwardenclient && docker compose up -d && docker compose logs -f
  7. The fullchain.pem and privkey.pem files should appear in the /opt/certwardenclient/certs folder.

  8. Mount them in the node’s docker-compose.yml (its web server is also called remnawave-nginx):

    root@server: ~
    cd /opt/remnanode && nano docker-compose.yml
    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
  9. Restart the node’s nginx:

    root@server: ~
    cd /opt/remnanode && docker compose down remnawave-nginx && docker compose up -d remnawave-nginx
  1. Add the Cert Warden server to Netbird, as its installation can be seen here

  2. In the Cloudflare control panel for your domain certwarden.yourdomain.com, specify the IP address of the Cert Warden server in the Netbird network

    Cert Warden Cloudflare

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