Self-hosted service
7ritn/VaulTLS avatar
7ritn/VaulTLS

VaulTLS in a container: a home lab certificate authority with OIDC and CRL hosting

Selfhostable web app to make managing mTLS certificates a breeze

1,176 stars65 forksRustGPL-3.0

At a glance

What is it?
VaulTLS is a self-hosted Rust and Vue web application that issues, stores and distributes mTLS and SSH certificates for a home lab. It replaces OpenSSL shell scripts with a database, a REST API and a revocation list served on its own port, at the cost of trusting one container with your keys.
Who is it for?
VaulTLS fits a self hoster who runs a reverse proxy, issues client certificates to a handful of services and has lost track of expiry dates. It is the wrong tool for a team that needs hardware key custody, per role authorization beyond a single admin flag, or a revocation list checked on every connection, and it is a poor fit for anyone unwilling to hand a container the CA private key.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem behind the project: expiry dates nobody can see

Home lab certificate handling usually starts as a directory of shell scripts that call OpenSSL, and it stays that way because every new service wants its own client certificate and nobody wants to remember which bundle expires in March. The stated motivation for VaulTLS was an aversion to messing with shell scripts and OpenSSL, paired with a more concrete gap: having no overview of when individual certificates expire. The design answers the second problem more directly than the first, with a web interface that lists the certificates a user owns and email notifications for expiration. The intended audience is the self hoster running a home lab behind a reverse proxy, not an enterprise public key infrastructure team. A certificate authority appears automatically during first setup, so the operator never issues a root certificate by hand.

A Rust backend and a Vue frontend delivered as one container image

The repository is split into backend/, frontend/ and container/ directories alongside a top level Containerfile, plus docs/ and tests/. The documented feature set ties those pieces together: X.509 certificate management, SSH certificate management, OpenID Connect authentication, email notifications for certificate expiration, a RESTful API for automation, and ACME CA support, which the documentation names as working with Traefik, acme.sh and other ACME clients. The single image shape matters operationally, because the API and the interface are served from the same origin, so the container needs one port published rather than a separate frontend service, and the reverse proxy in front of it has one upstream to route. Further API documentation is served from the endpoint /api, which is unusual enough to be worth noting before you plan an automation script.

Running the container: one required secret and a reverse proxy in front

Installation is container only, and the documentation is explicit that the app should sit behind a reverse proxy that handles TLS. The API secret is mandatory and must be a 256 bit base64 encoded string, which the documentation generates with openssl rand -base64 32. The example publishes host port 5173 to container port 80 and keeps state in a named volume mounted at /app/data, so an upgrade replaces the image without touching the data. If you deliberately want to reach the interface over plain HTTP, you set VAULTLS_INSECURE=true, and the documentation warns implicitly by putting the variable behind that condition rather than treating it as a default. Three environment variables, VAULTLS_API_SECRET, VAULTLS_DB_SECRET and VAULTLS_OIDC_SECRET, can also be delivered as container secrets; VaulTLS looks for them under /run/secrets/<ENV_NAME> and the path can be redirected.

bash
openssl rand -base64 32
bash
podman run -d \
  --name vaultls \
  -p 5173:80 \
  -v vaultls-data:/app/data \
  -e VAULTLS_API_SECRET="[VAULTLS_API_SECRET]" \
  -e VAULTLS_URL="https://vaultls.example.com/" \
  ghcr.io/7ritn/vaultls:latest

OIDC login, and the Zitadel audience that must be set by hand

Users reach the interface with a password, with OpenID Connect, or with both. The documentation describes a linking rule that is worth understanding before you roll this out: when someone logs in via OIDC for the first time, their email address is matched against existing VaulTLS users and the accounts are linked, and when no match exists a new user is created. The provider side needs a confidential client whose callback lands on /api/auth/oidc/callback, and VaulTLS itself is configured through four variables, VAULTLS_OIDC_AUTH_URL, VAULTLS_OIDC_CALLBACK_URL, VAULTLS_OIDC_ID and VAULTLS_OIDC_SECRET. One configuration detail is a genuine trap: providers such as Zitadel place several elements in the aud claim, and the extra trusted audiences have to be listed as a comma separated value in VAULTLS_OIDC_ADDITIONAL_AUDIENCES, which the documentation states cannot be configured through the web interface and must go into the environment or the settings file.

yaml
- client_id: "[client_id]"
  client_name: "vautls"
  client_secret: "[client_secret_hash]"
  public: false
  authorization_policy: "one_factor"
  pkce_challenge_method: "S256"
  redirect_uris:
    - "https://vaultls.example.com/api/auth/oidc/callback"
  scopes:
    - "openid"
    - "profile"
    - "email"
  userinfo_signed_response_alg: "none"

Every download is a PKCS#12 bundle, and reverse proxies usually refuse that format

Downloaded certificates arrive in PKCS#12 form, a bundle holding the public certificate together with the private key. The password policy is a setting with three options: passwords optional and blank, passwords required and supplied by the user, or randomly generated by the system. The documentation states that these passwords are kept in the database and are shown in the web interface only when the user clicks to view the password, so a database backup carries the material needed to open the bundles. Server certificates work the same way but add SAN DNS entries, which is the piece most reverse proxies actually read. Because a PKCS#12 file holds both halves in one container, a proxy that expects a certificate file and a key file separately needs the bundle split, and the documentation gives the openssl invocation for exactly that. The CA certificate itself is exposed as a file at /app/data/ca/ca.cert and through the API endpoint /api/certificates/ca/download.

Revocation runs through CRL files, served without authentication on port 2277

A TLS certificate cannot simply be deleted, because its validity period is encoded in the certificate itself, so VaulTLS uses certificate revocation lists. The CRL files are stored under /app/data/crl/, can be downloaded from the CA tab of the frontend, and are reachable through the API path /api/certificates/ca/<id>/crl. Two properties of that setup deserve scrutiny. The first is that the documentation says these files do not require authentication to be accessed, and the second is that VaulTLS hosts the CRL directory on port 2277, which can be exposed publicly so that validating servers can fetch it. Combined with the CRL Distribution Point extension, a client certificate can point at that URL, which is what makes revocation work for peers that are not part of your reverse proxy chain. Exposing an unauthenticated port for this reason is a deliberate trade, and it means the port should be firewalled or published only if the CRL really must be reachable from outside.

sh
openssl pkcs12 -in INFILE.p12 -out OUTFILE.crt -nokeys
openssl pkcs12 -in INFILE.p12 -out OUTFILE.key -nodes -nocerts

How this differs from OpenSSL scripts and from a plain ACME client

The comparison the project itself invites is with the OpenSSL shell scripts it replaces, and the difference is ownership of state. A script writes key material to disk and leaves the operator to track validity dates in their head or in a spreadsheet; VaulTLS keeps certificates, their passwords and their expiry in a database, exposes them through a web interface and a REST API, and sends email notifications when they approach expiry. The second difference is the certificate type. ACME clients such as the Traefik and acme.sh integrations the project lists are built around obtaining certificates from an ACME CA, while VaulTLS centres on issuing and distributing client and server certificates from your own CA to services behind your own proxy. The two approaches overlap at the edges, because VaulTLS itself supports ACME CAs, but a setup driven by ACME does not produce the client certificates and the CRL file that a mutual TLS deployment needs.

The database secret cannot be undone, and the licence travels with the code

Setting VAULTLS_DB_SECRET encrypts the database. The documentation is blunt about the consequence: data is retained and it is not possible to go back, so the value must be stored somewhere durable before it is set, and losing it means losing access to the stored material. This is a one way door in a home lab that usually has no second copy of anything. Logging follows the same pattern of honest limits: the default level is moderate, VAULTLS_LOG_LEVEL accepts error, warn, info, debug and trace, and the documentation points out that logs can contain secrets and should be censored before being attached to a bug report. The repository carries a GPL-3.0 licence, so anyone planning to modify and redistribute the code should read that licence text directly rather than take any summary as guidance. On maintenance, the last push was on 2026-10-14, and tagged releases v1.2.0, v1.3.0 and v1.4.0 were published on 2026-06-02, 2026-07-10 and 2026-08-23, so the visible surface is small and moves in a way that expects occasional manual attention when an environment variable or a container secret changes.

Editorial conclusion

VaulTLS fits a self hoster who runs a reverse proxy, issues client certificates to a handful of services and has lost track of expiry dates. It is the wrong tool for a team that needs hardware key custody, per role authorization beyond a single admin flag, or a revocation list checked on every connection, and it is a poor fit for anyone unwilling to hand a container the CA private key. Before adopting it, confirm that your reverse proxy can load the CA file from /app/data/ca/ca.cert, decide how you will store VAULTLS_API_SECRET and VAULTLS_DB_SECRET, and read the GPL-3.0 licence before modifying the code you plan to distribute.

Frequently asked questions

What is VaulTLS and what is it used for?

VaulTLS is a selfhostable web app for managing mutual TLS certificates in a home lab, written in Rust with a Vue.js frontend. It creates a certificate authority on first setup, lets only admins create certificates, and lets each user download and view the passwords for certificates issued to them.

How do I install VaulTLS?

Installation is container based, using the image ghcr.io/7ritn/vaultls:latest with podman or Docker, and the app is meant to run behind a reverse proxy that terminates TLS. VAULTLS_API_SECRET is required and must be a 256 bit base64 encoded string, generated with openssl rand -base64 32, and state lives in a volume mounted at /app/data.

Can I use OpenID Connect instead of a password with VaulTLS?

Yes. Users can log in with a password, with OIDC, or with both, and a first OIDC login links the account to an existing VaulTLS user by email address, creating a new user when no match is found. Extra trusted audiences for providers such as Zitadel must be listed in VAULTLS_OIDC_ADDITIONAL_AUDIENCES because that setting cannot be made in the web interface.

How does VaulTLS revoke a certificate?

Revocation uses certificate revocation lists stored as files under /app/data/crl/, downloadable from the CA tab and through /api/certificates/ca/<id>/crl, and served on port 2277 for peers that need to check them. The documentation notes that these files do not require authentication to be accessed.

Is the VaulTLS database encrypted?

It can be, by setting VAULTLS_DB_SECRET. The documentation states that the data is retained and that it is not possible to go back, so the secret has to be stored reliably before it is set.

Official sources

  1. 7ritn/VaulTLS on GitHub
  2. Issues
  3. License: GPL-3.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/7ritn-vaultls.svg)](https://hysenlabs.com/projects/7ritn-vaultls)

Was this useful?