Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 25 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# ACME Server as a Registration Authority
# About

`acme-proxy` is a standalone ACME server built on [step-ca](https://github.com/smallstep/certificates) that operates in [registration authority (RA)](https://smallstep.com/docs/registration-authorities/) mode. It accepts certificate orders and validates certificate requests using the ACME protocol (RFC 8555), but does **NOT** sign certificates or store private keys.

## Documentation

Checkout our [documentation site](https://software.es.net/acme-proxy) for detailed examples on usage, installation instructions, configuration etc.

## How It Works

`acme-proxy` runs as an ACME server inside your enterprise environment, acting as an intermediary between your internal infrastructure and an external certificate authority service (such as Sectigo). When a client successfully completes an ACME challenge, `acme-proxy` forwards the certificate signing request to an external certificate authority (CA) that supports External Account Binding (EAB). The external CA signs the certificate and returns it to the client through `acme-proxy`.
`acme-proxy` runs as an ACME server inside your trusted network, acting as an intermediary between your internal infrastructure and an external certificate authority service (such as Sectigo). When a client successfully completes an ACME challenge, `acme-proxy` forwards the certificate signing request to an external certificate authority (CA) that supports External Account Binding (EAB). The external CA signs the certificate and returns it to the client through `acme-proxy`.

**Certificate Request Flow:**

Expand All @@ -31,25 +35,11 @@ This architecture addresses typical enterprise constraints that prevent direct c
- Legacy DNS infrastructure lacks REST API support or ACME client integration
- Security policies restrict distribution of API tokens or TSIG keys for large DNS zones

For more information on DNS-01 security considerations:
For more information on security considerations when using DNS-01 challenge:

- [EFF: Technical Deep Dive on ACME DNS Challenge Validation](https://www.eff.org/deeplinks/2018/02/technical-deep-dive-securing-automation-acme-dns-challenge-validation)
- [LetsEncrypt: DNS-01 Challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge)

## Benefits

Using ACME with commercial CAs in enterprise environments provides several advantages:

**Trusted Certificates:**

- Certificates are signed by publicly trusted CAs are already in system trust stores
- Eliminates the operational burden of distributing and maintaining custom root certificates across endpoints, servers, and client devices

**Automation and Self-Service:**

- Leverage standard ACME clients (Certbot, acme.sh, cert-manager.io) for certificate issuance, automatic renewals.
- Enable self-service certificate requests for development teams

## Quick Start

```sh
Expand Down Expand Up @@ -83,17 +73,6 @@ Requirements: Go >= 1.25
❯ cd acme-proxy && make
```

### Using Docker

You can either use our [pre-built container images](https://github.com/esnet/acme-proxy/pkgs/container/acme-proxy) or you can build the image yourself.

**DIY - Build docker image**

```sh
❯ git clone https://github.com/esnet/acme-proxy.git
❯ cd acme-proxy && docker build -t acme-proxy:latest .
```

## Usage

Review and update configuration options in [ca.json](./ca.json) before starting the acme-proxy server.
Expand All @@ -102,7 +81,7 @@ Review and update configuration options in [ca.json](./ca.json) before starting
vim ca.json
```

The most important parts of the config are -
Checkout our [official docs](https://software.es.net/acme-proxy/install/#configuration) for full set of configuration options. For quick start the most relevant config bits are:

```json
"dnsNames": ["acmeproxy.example.com"],
Expand All @@ -117,7 +96,7 @@ The most important parts of the config are -
"metrics": {
"enabled": true,
"port": 9234,
"dataSource": "db/metrics"
"dataSource": "/opt/acme-proxy/db/metrics"
}
},
...
Expand Down Expand Up @@ -166,41 +145,6 @@ badger 2025/07/15 22:12:24 INFO: Replay took: 5.99µs
2025/07/15 22:12:33 Serving HTTPS on proxy.example.com:443 ...
```

When using acme-proxy with docker take a note of the bind mount and port

```sh
$ docker run -itd -p 443:443 -v ./ca-dev.json:/acme-proxy/config/ca.json --name acme-proxy acme-proxy:latest
29c1ca374832dc50d3215b404f620c2a08d988c30f630464bf9d7d35aa44345f

$ docker logs acme-proxy
2026/03/17 23:04:06 Building new tls configuration using step-ca x509 Signer Interface
2026/03/17 23:04:07 [INFO] acme: Registering account for certadmin@example.com
2026/03/17 23:04:07 INFO processing certificate request domains=[proxy.example.com]
2026/03/17 23:04:07 [INFO] [proxy.example.com] acme: Obtaining bundled SAN certificate given a CSR
2026/03/17 23:04:08 [INFO] [proxy.example.com] AuthURL: https://acme.sectigo.com/v2/InCommonRSAOV/authz/jQJHRdd-0kKdm-JVQVhjHQ
2026/03/17 23:04:08 [INFO] [proxy.example.com] acme: authorization already valid; skipping challenge
2026/03/17 23:04:08 [INFO] [proxy.example.com] acme: Validations succeeded; requesting certificates
2026/03/17 23:04:08 [INFO] Wait for certificate [timeout: 30s, interval: 500ms]
2026/03/17 23:04:13 [INFO] [proxy.example.com] Server responded with a certificate.
2026/03/17 23:04:13 INFO obtained certificate from external CA domains=[proxy.example.com]
2026/03/17 23:04:13 Starting Smallstep CA/0000000-dev (linux/amd64)
2026/03/17 23:04:13 Documentation: https://u.step.sm/docs/ca
2026/03/17 23:04:13 Community Discord: https://u.step.sm/discord
2026/03/17 23:04:13 Config file: /acme-proxy/config/ca.json
2026/03/17 23:04:13 The primary server URL is https://proxy.example.com:443
2026/03/17 23:04:13 Root certificates are available at https://proxy.example.com:443/roots.pem
2026/03/17 23:04:13 Serving HTTPS on :443 ...

$ curl -s https://proxy.example.com/acme/acme/directory | jq .
{
"newNonce": "https://proxy.example.com/acme/acme/new-nonce",
"newAccount": "https://proxy.example.com/acme/acme/new-account",
"newOrder": "https://proxy.example.com/acme/acme/new-order",
"revokeCert": "https://proxy.example.com/acme/acme/revoke-cert",
"keyChange": "https://proxy.example.com/acme/acme/key-change"
}
```

### Obtaining a certificate

While the example below uses `acme.sh` as the ACME client, we've also tested using `certbot` with equal success.
Expand Down Expand Up @@ -283,11 +227,11 @@ Certificate:

```

We have our certificate signed by InCommon 🎉
We have our certificate signed by our certificate authority i.e InCommon 🎉

### Renewing a certificate

Issuing a certificate is *generally* not a problem in enterprise environments. But the ability to reliably renew certificates and reload services gracefully post renewal is. I am using the `--force` flag for renewal only because the default configuration in ACME clients only performs automatic renewal `1 < N < 30` number of days before certificate expiration.
Issuing a certificate is *generally* not a problem in enterprise environments. But the ability to automatically renew certificates and reload services gracefully post renewal is. I am using the `--force` flag for renewal only because the default configuration in ACME clients only performs automatic renewal `1 < N < 30` number of days before certificate expiration.

```sh
$ ./acme.sh --renew --domain myserver.example.com --force
Expand Down Expand Up @@ -342,3 +286,17 @@ N+c9XyDLAiEAkbrRKBsYc8YSgYviREF9u+gz7jK5JY2dsaRatEfb8Eg=
```

Cert renewal was a success! ✨

## Benefits

Using ACME with commercial CAs in enterprise environments provides several advantages:

**Trusted Certificates:**

- Certificates are signed by publicly trusted CAs are already in system trust stores
- Eliminates the operational burden of distributing and maintaining custom root certificates across endpoints, servers, and client devices

**Automation and Self-Service:**

- Leverage standard ACME clients (Certbot, acme.sh, cert-manager.io) for certificate issuance, automatic renewals.
- Enable self-service certificate requests for development teams
7 changes: 1 addition & 6 deletions develop/contribute.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
+++
title = 'Contributing'
weight = 50
+++

# Setup development environment

1. Install `go >= 1.25`
Expand All @@ -14,5 +9,5 @@ weight = 50

1. Fork the repo
2. In your fork, create a new branch for your work
3. Commit & push changes to your fork
3. Add changes in your branch, Commit & push changes to your forked repo
4. Submit a pull request
6 changes: 0 additions & 6 deletions develop/externalcas.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
+++
title = 'External CAs'
weight = 30
BookToC = true
+++

# ACME server as Registration Authority

See [upstream docs](#upstream-docs) section for more background on what registration authority, CAS are and how those concepts fits into step-ca architecture.
Expand Down
6 changes: 0 additions & 6 deletions develop/maintenance.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
+++
title = 'Maintenance'
weight = 40
BookToC = true
+++

# Guide to patching upstream related changes

- While smallstep/certifiates is meant to serve as the upstream Go module for acme-proxy, we have to maintain some patches/fixes ourselves until they get merged upstream. Our patched version of step-ca is currently maintained in a forked repo [esnet/certificates](https://github.com/esnet/certificates).
Expand Down
4 changes: 2 additions & 2 deletions docs/config/_default/hugo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
baseURL = 'http://docs.acme-proxy.es.net'
title = 'ACME Proxy'
baseURL = 'http://docs.acme-proxy.es.net/'
title = 'acme-proxy'
theme = 'hugo-book'

# Book configuration
Expand Down
34 changes: 30 additions & 4 deletions docs/content/_index.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
+++
title = 'ACME Proxy'
title = 'acme-proxy'
+++

# What is ACME Proxy?
## What is acme-proxy?

`acme-proxy` is a standalone ACME server built on [step-ca](https://github.com/smallstep/certificates) that operates in [registration authority (RA)](https://smallstep.com/docs/registration-authorities/) mode. It runs as a standalone server inside your enterprise environment, acting as an intermediary between your internal infrastructure and an external certificate authority service (such as Sectigo). It accepts certificate orders and validates certificate requests using the ACME protocol (RFC 8555), but does **NOT** sign certificates or store private keys.
`acme-proxy` is a standalone ACME server built on [step-ca](https://github.com/smallstep/certificates) that operates in [registration authority (RA)](https://smallstep.com/docs/registration-authorities/) mode. It runs as a standalone server inside your enterprise environment, acting as an intermediary between your internal infrastructure and an external certificate authority service (such as Sectigo, DigiCert or ZeroSSL). It accepts certificate orders and validates certificate requests using the ACME protocol (RFC 8555), but does **NOT** sign certificates or store private keys.

{{< image src="/assets/highlevel-flow.png" alt="sequence" >}}

# Certificate Request Flow
## Certificate issuance flow

1. Your internal server (behind a firewall perimeter) requests a certificate from `acme-proxy` using standard ACME clients like certbot, acme.sh or cert-manager.io if you're using Kubernetes.
2. `acme-proxy` presents cryptographic challenges to verify domain ownership
Expand All @@ -17,3 +17,29 @@ title = 'ACME Proxy'
5. `acme-proxy` retrieves the certificate bundle and returns it to your server

{{< image src="/assets/sequence.png" alt="sequence" >}}

## Connectivity Requirements

For the ACME certificate request issuance, renewal flow to work correctly, make sure your any internal firewalls, ACLs, IPtables rules permit the following traffic.

**Client to acme-proxy (HTTPS/443)**

Your servers running certbot must be able to connect to acme-proxy over HTTPS.

```
Source myserver.example.com
Destination acme-proxy.example.com
Protocol https (443)
Action allow
```

**acme-proxy to Client (HTTP/80)**

`acme-proxy` validates HTTP-01 challenges by connecting to your servers directly on port 80. Your servers must allow inbound HTTP/80 from acme-proxy's IP — not from the public internet. This is the key security benefit: HTTP/80 exposure is limited to a trusted internal host rather than the global internet which is the case when using LetsEncrypt.

```
Source acme-proxy.example.com
Destination myserver.example.com
Protocol http (80)
Action allow
```
52 changes: 24 additions & 28 deletions docs/content/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ For certificate issuance commands and per-scenario usage, see [user.md](./user.m

## Installing ACME Clients

### Certbot

{{< tabs >}}
{{% tab "Certbot" %}}
> **Note:** Certbot's actively maintained distribution is via Snap. The `.deb` packages available in apt repositories are no longer maintained by the Certbot project and ship outdated versions.

Install via Snap:
Expand All @@ -49,11 +49,9 @@ For the Apache plugin:
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-apache
```
{{% /tab %}}

---

### acme.sh

{{% tab "acme.sh" %}}
**Debian / Ubuntu:**

```bash
Expand All @@ -71,11 +69,9 @@ sudo dnf install -y acme.sh socat
The package installs the binary to `/usr/bin/acme.sh`. Use `/etc/acme.sh` as the configuration home for system-wide installations (passed via `--home` in all commands).

> **`socat` is required for standalone mode.** acme.sh uses `socat` to bind port 80 for HTTP-01 challenges in standalone mode. It is installed above alongside acme.sh. This is not required if you use NGINX or Apache plugin mode.
{{% /tab %}}

---

### Lego

{{% tab "Lego" %}}
Lego has no official packages in major Linux distribution repositories. Install the release binary directly:

```bash
Expand All @@ -88,29 +84,33 @@ lego --version
```

> Verify the checksum from the [GitHub releases page](https://github.com/go-acme/lego/releases) before deploying to production. Pin `LEGO_VERSION` in your configuration management tool and treat upgrades as a deliberate change.
{{% /tab %}}
{{< /tabs >}}

---

## Account Registration

Each ACME client must register an account with acme-proxy before any certificates can be issued. This is a one-time step per host.

### Certbot

{{< tabs >}}
{{% tab "Certbot" %}}
Certbot registers automatically on first use. No separate registration step is required.
{{% /tab %}}

### acme.sh

{{% tab "acme.sh" %}}
```bash
sudo acme.sh --register-account \
--server https://acme-proxy.example.com/acme/acme/directory \
--email admin@example.com \
--home /etc/acme.sh
```
{{% /tab %}}

### Lego

{{% tab "Lego" %}}
Lego registers automatically on the first `run` invocation. No separate registration step is required.
{{% /tab %}}
{{< /tabs >}}

---

Expand All @@ -124,10 +124,8 @@ Replacing cron-based renewal with systemd timers provides:

All service units below set `SyslogIdentifier` so logs can be filtered by tag regardless of which syslog daemon is in use.

---

### Certbot

{{< tabs >}}
{{% tab "Certbot" %}}
The Snap-installed certbot ships `certbot.timer` and `certbot.service` units automatically. Enable the timer and confirm it is active:

```bash
Expand All @@ -153,11 +151,9 @@ sudo systemctl daemon-reload
```bash
sudo certbot renew --dry-run
```
{{% /tab %}}

---

### acme.sh

{{% tab "acme.sh" %}}
acme.sh's `--cron` flag iterates over all configured certificates and renews those expiring within 30 days. A single service and timer unit covers all certificates on the host.

**Create the service unit:**
Expand Down Expand Up @@ -209,11 +205,9 @@ sudo systemctl enable --now acme-renewal.timer
systemctl status acme-renewal.timer
systemctl list-timers acme-renewal.timer
```
{{% /tab %}}

---

### Lego

{{% tab "Lego" %}}
Lego has no built-in renewal scheduling. Create service and timer units manually.

Unlike acme.sh and certbot, lego's `renew` command targets one domain at a time. If you manage multiple certificates, use a wrapper script.
Expand Down Expand Up @@ -297,6 +291,8 @@ sudo systemctl enable --now lego-renewal.timer
systemctl status lego-renewal.timer
systemctl list-timers lego-renewal.timer
```
{{% /tab %}}
{{< /tabs >}}

---

Expand Down
5 changes: 0 additions & 5 deletions docs/content/firewall.md

This file was deleted.

Loading
Loading