Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Quick Start

Get acme-proxy running in a couple of minutes with an auto-generated Local CA.

Step 1 — Write a configuration file

acme-proxy serves ACME only through profiles, so at least one enabled profile is required — the server refuses to start without one. Everything else has a working default.

By default the server also performs real domain-control validation (HTTP-01). To test a client against the proxy locally without routing port 80 or configuring DNS, this quick start turns validation off with challenge.bypass.

Create config.toml in your current directory:

[challenge]
# Testing only. See "Moving to production" below.
bypass = true

[profiles.default]

[profiles.default] is not empty by accident — a profile’s enabled key defaults to true, so naming the profile is all that is required. The profile name becomes part of the URL, and of every kid a client stores.

Step 2 — Run the server

acme-proxy serve

serve is also the default, so a bare acme-proxy does the same thing.

There is no --config flag. The server reads config.toml from the current working directory; point it elsewhere with ACME_PROXY_CONFIG:

ACME_PROXY_CONFIG=/etc/acme-proxy/config.toml acme-proxy serve

(The extension may be omitted — the format is then inferred.) A missing configuration file is not an error: the server falls back to defaults, which is why an environment-only deployment works.

Individual keys can also be overridden with ACME_PROXY_* environment variables; see the Configuration Reference.

The server binds [::]:3000 and serves the ACME directory at http://localhost:3000/profile/default/directory. On first run it creates sqlite.db, ca.pem and ca.key in the working directory.

Step 3 — Request a certificate

Point any standard ACME client at the profile’s directory URL. Examples for internal.example.com:

certbot

certbot certonly \
  --server http://localhost:3000/profile/default/directory \
  --standalone \
  --domain internal.example.com \
  --email admin@example.com \
  --agree-tos \
  --no-eff-email

acme.sh

acme.sh --issue \
  --server http://localhost:3000/profile/default/directory \
  -d internal.example.com \
  --standalone

lego

lego --server http://localhost:3000/profile/default/directory \
  --email admin@example.com \
  --domains internal.example.com \
  --http \
  run

Traefik

In Traefik’s static configuration (traefik.yml):

certificatesResolvers:
  myresolver:
    acme:
      caServer: http://localhost:3000/profile/default/directory
      email: admin@example.com
      httpChallenge:
        entryPoint: web

Caddy

In your Caddyfile:

internal.example.com {
    tls admin@example.com {
        ca http://localhost:3000/profile/default/directory
    }
    respond "Hello, world!"
}

What just happened?

  1. The client fetched the directory from acme-proxy.
  2. It registered an account and created a new order.
  3. acme-proxy offered an HTTP-01 challenge.
  4. Because challenge.bypass = true, the proxy marked the challenge valid the moment the client triggered it, without making any network request back to the client’s responder.
  5. The proxy signed the CSR with its auto-generated local ECDSA CA and returned the certificate.

The certificate is signed by a CA nothing trusts yet. See Trusting the CA for how to install ca.pem where your clients will accept it.

Step 4 — Moving to production

The defaults are safe; this quick start deliberately relaxed them. Before exposing the server:

  1. Remove the bypass. Delete challenge.bypass = true so acme-proxy actually validates domain control. With bypass on, [filter] is the only access control there is — which is exactly why bypass is not the default. See Challenge Validation.
  2. Enable filters. Configure allowed_ip, identifiers or ipam to restrict which clients may request which names. See Filters & Policies.
  3. Choose where state lives. The defaults write sqlite.db and the CA material to the current working directory. Set database.url and signer.local_ca.cert_path / signer.local_ca.key_path to permanent paths — note these are the CA’s files, distinct from server.tls.cert_path / server.tls.key_path, which belong to the HTTPS listener.
  4. Serve over HTTPS. RFC 8555 §6.1 expects ACME over HTTPS: either put the server behind a reverse proxy, or turn on TLS termination.