Deployment guide

Certimate 0.4.36

Overview

Certimate is an open source, self-hosted tool that manages the whole life of SSL/TLS certificates. In a visual editor, you build workflows that request a certificate from an ACME certificate authority (Let's Encrypt, ZeroSSL, Google Trust Services, SSL.com and others) with a DNS-01 or HTTP-01 challenge. The workflow then deploys the certificate to the places that need it (servers over SSH, Kubernetes, CDNs, load balancers, cloud certificate stores, webhooks and many more), renews it on a schedule and sends notifications. Provider credentials are kept in one place and reused by your workflows.

This SecureAnyCloud image runs Certimate 0.4.36 on Ubuntu 24.04 on Amazon EC2 as a hardened, ready-to-use server. It is one version of the SecureAnyCloud identity, secrets and PKI listing on AWS Marketplace. You select it as the software version when you subscribe and launch.

What you get:

  • nginx in front, Certimate behind it. nginx is the only public listener (ports 80 and 443). Certimate listens on 127.0.0.1:8090, the loopback address only.
  • HTTPS from the first boot. nginx serves the console with a certificate that is generated on your instance's first boot (an end-entity certificate, not a certificate authority), and port 80 redirects to HTTPS. First boot sets the instance's name from its own EC2 public DNS name. You can change it to your own domain.
  • No default login. Upstream Certimate creates the administrator admin@certimate.fun with the password 1234567890 on a fresh install. This image never does. First boot creates the administrator admin@certimate.local with a random password that is unique to your instance, before any server is reachable, and the upstream default is refused.
  • No anonymous accounts, throttled sign-ins. Certimate is built on PocketBase, whose default users collection lets anyone on the internet create an account. On this image, that is switched off. PocketBase's own rate limiter is on (two sign-ins per three seconds per client address), and the raw PocketBase dashboard (/_/) is reachable only through an SSH tunnel.
  • Real client addresses, fixed host name. nginx passes the real TCP peer to Certimate (it overwrites X-Real-IP and X-Forwarded-For), and PocketBase trusts only that header, so its rate limits and request logs cannot be fooled. Requests for any other host name are dropped.
  • Metadata access blocked. Certimate calls URLs that you configure (webhooks, notification channels, deployment targets, ACME directories). Certimate, nginx and the first-boot service are blocked from the instance metadata service, so none of those features can reach it, and the AMI requires IMDSv2.
  • Built from source. The Certimate binary is a SecureAnyCloud rebuild of the upstream v0.4.36 tag on a supported Go release, with patched Go modules and a console rebuilt with fixed JavaScript libraries (see Step 13). The China Mobile eCloud providers are not included in this build.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

On the image: Certimate 0.4.36 (/usr/local/bin/certimate, with the web console built in), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.

Prerequisites

  • An AWS account that is subscribed to the listing, an EC2 key pair, and a VPC subnet with a route to the internet. Certimate needs outbound HTTPS to reach your certificate authority and your DNS and deployment providers.
  • A security group that allows TCP 22 (SSH, also used for the dashboard tunnel) from your own networks only, and TCP 443 (the console) and TCP 80 (redirects to HTTPS) from the networks you manage certificates from. Do not open 8090, because Certimate listens on the loopback address only.
  • For production, a DNS name that you control (for example certs.example.com) and, ideally, a certificate from a certificate authority for the console itself. Certimate can issue it.
1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Select Certimate 0.4.36 on Ubuntu 24.04 as the software version, select your Region, then choose Launch through EC2. Choose m5.large, your key pair, and a security group as described above.

To launch with the AWS CLI instead, replace the values in angle brackets:

aws ec2 run-instances --image-id <ami-id-from-the-listing> --instance-type m5.large \
  --key-name <your-key-pair> --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=certimate}]'

First boot takes a few seconds after the instance is running. To use your own name from the start, put it in /etc/sac/certimate-domain with user data, for example a cloud-init write_files entry. Otherwise, first boot uses the instance's public DNS name, or its public IP address if it has no DNS name.

2

Connect to your instance

Operating system SSH user

Ubuntu 24.04 ubuntu

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

systemctl is-active certimate-firstboot certimate nginx
active active active

Only SSH, HTTP and HTTPS are public. Certimate listens on the loopback address. Confirm the listeners:

sudo ss -tlnp | grep -E ':(22|80|443|8090) ' | awk '{print $4}' | sort -u
0.0.0.0:22 0.0.0.0:443 0.0.0.0:80 127.0.0.1:8090 [::]:22 [::]:443 [::]:80
curl -sk https://127.0.0.1/api/health
{"message":"API is healthy.","code":200,"data":{}}
4

Get your sign-in and the certificate fingerprint

sudo cat /root/certimate-credentials.txt

The file (root only, mode 0600) holds the console address, this instance's administrator and the SHA-256 fingerprint of its certificate, followed by a short how-to. The example below shows the password as <redacted>:

# Certimate 0.4.36 - generated on first boot by certimate-firstboot.service. Unique to this instance. Store it safely. certimate.url=https://ec2-35-172-134-191.compute-1.amazonaws.com/ certimate.admin_email=admin@certimate.local certimate.admin_password=<redacted> certimate.tls.sha256_fingerprint=AB:9E:02:77:56:10:83:79:B4:BC:DE:70:BF:94:8B:47:EC:71:7A:F2:56:77:78:16:AD:1A:84:4A:0C:3C:88:7F

The login banner also shows the name, every address that the instance answers to, and the certificate fingerprint. You can print them with:

certimate-set-domain --show
domain: ec2-35-172-134-191.compute-1.amazonaws.com console: https://ec2-35-172-134-191.compute-1.amazonaws.com/ also served: ec2-35-172-134-191.compute-1.amazonaws.com 35.172.134.191 172.31.89.6 certificate: SHA-256 AB:9E:02:77:56:10:83:79:B4:BC:DE:70:BF:94:8B:47:EC:71:7A:F2:56:77:78:16:AD:1A:84:4A:0C:3C:88:7F

The certificate is self-signed and unique to your instance, so your browser shows a warning. Open the certificate details in the browser and check that its SHA-256 fingerprint matches certimate.tls.sha256_fingerprint. Only then continue.

Do not add the certificate to your operating system's trust store. For production, replace it with a certificate from a certificate authority (Step 10).

5

Sign in to the console

Browse to the certimate.url address:

Upstream's default administrator does not exist on this image. A sign-in as admin@certimate.fun with 1234567890 is refused.

Sign in with certimate.admin_email and certimate.admin_password from the credentials file. The dashboard opens.

The console allows two sign-ins per three seconds from one client address. Further attempts get "Too many requests" for a few seconds.

6

Add the credentials of your DNS and deployment providers

Open Credentials and choose Create credential. Pick the provider that hosts your domain's DNS (for a DNS-01 challenge) or the place you deploy certificates to.

Each provider asks only for what it needs, for example a scoped API token for Cloudflare.

Use credentials scoped to what Certimate must do. For a DNS-01 challenge, that means edit rights on the TXT records of the zones you issue for. Certimate stores credentials in its database under /var/lib/certimate/pb_data, readable only by the certimate service user.

  • AWS: Certimate 0.4.29 and later can take AWS credentials from the instance metadata service. That option does not work on this image, because the metadata service is blocked for Certimate (Step 9). Create an IAM user (or a role that you assume with access keys) limited to the Route 53 zones and certificate services you use, and enter its keys.
  • China Mobile eCloud still appears in the provider list, but its DNS and deployment providers are not included in this build (Step 13). A workflow that uses them fails with "provider ... not registered".
7

Build a workflow

Open Workflows and create a workflow. A typical workflow has four stages: Start (manual or a schedule), Apply (the domains, the certificate authority, the challenge type and the DNS credential), Deploy (one node per target) and Notify. Run it once by hand, then let the schedule renew the certificate before it expires.

Issued certificates appear under Certificates, with their expiry dates. You can download or revoke them there.

8

Check the server from the command line

One command checks the whole chain on the instance. It confirms that the console and health check answer over HTTPS, that the default administrator is refused, and that the per-instance administrator signs in and reads a protected collection. It also confirms that a request without a token, a wrong password and anonymous sign-up are all refused, and that the PocketBase dashboard is served only on the loopback address:

sudo /usr/local/sbin/certimate-cred-roundtrip.sh
CERTIMATE_ROUNDTRIP_OK health=200 console=200 default-admin=400 admin=200 protected=200 no-token=403 wrong-password=400 anon-signup=403 dashboard-public=403 dashboard-loopback=200
9

Check the security posture

The certificate is an end-entity certificate for the instance's name and addresses (plus localhost for the tunnel). It cannot sign other certificates:

openssl x509 -in /etc/ssl/certimate/certimate.crt -noout -subject -ext basicConstraints,keyUsage,extendedKeyUsage,subjectAltName
subject=CN = ec2-35-172-134-191.compute-1.amazonaws.com, O = sac X509v3 Subject Alternative Name: DNS:ec2-35-172-134-191.compute-1.amazonaws.com, IP Address:35.172.134.191, IP Address:172.31.89.6, DNS:localhost, IP Address:127.0.0.1 X509v3 Basic Constraints: critical CA:FALSE X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication

The server refuses TLS 1.0 and 1.1, and a request for a host name that the instance does not serve gets no answer:

echo | openssl s_client -tls1_1 -cipher 'DEFAULT@SECLEVEL=0' -connect 127.0.0.1:443 2>&1 | grep -o 'alert protocol version' | head -1 code=$(curl -sk -o /dev/null -w '%{http_code}' -H 'Host: evil.example' "https://$(hostname -I | awk '{print $1}')/api/health" || true);
echo "$code"; [ "$code" = 000 ]
alert protocol version 000

nginx passes the real client address to Certimate, never a client-supplied one:

grep -E 'proxy_set_header +X-(Real-IP|Forwarded-For)' /etc/nginx/sac/certimate-proxy.conf
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;

The services are blocked from the instance metadata service. The first command shows the systemd rule on each service. The second command makes a metadata request from inside Certimate's own service, as its user, and gets no connection (000):

systemctl show -p IPAddressDeny certimate nginx certimate-firstboot
sudo bash -c 'cg=$(systemctl show -p ControlGroup --value certimate.service); echo $$ > /sys/fs/cgroup$cg/cgroup.procs; setpriv --reuid=certimate --regid=certimate --init-groups curl -s -m 5 -o /dev/null -w "imds from certimate: %{http_code}\n" -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 60" http://169.254.169.254/latest/api/token; true' | grep -x 'imds from certimate: 000'
IPAddressDeny=::ffff:169.254.169.254/128 fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 IPAddressDeny=::ffff:169.254.169.254/128 fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 IPAddressDeny=::ffff:169.254.169.254/128 fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32 imds from certimate: 000

The same block applies to everything that Certimate sends on your behalf. A webhook notification test aimed at the metadata service never gets an answer. The image's API helper signs in with the credentials file and runs Certimate's own Test of a webhook notification channel:

T=$(sudo mktemp -d /run/cm-guide.XXXXXX)
sudo python3 -I /usr/local/lib/sac/certimate/cmapi.py login https://127.0.0.1 /root/certimate-credentials.txt $T/tok
R=$(sudo python3 -I /usr/local/lib/sac/certimate/cmapi.py notify https://127.0.0.1 $T/tok http://169.254.169.254/latest/meta-data/)
sudo rm -rf $T echo "$R" | grep -o 'Post "http://169.254.169.254/latest/meta-data/": context deadline exceeded' case "$R" in "200 0 "*) echo "metadata webhook: REACHED"; false ;; *) echo "metadata webhook: blocked (no answer)" ;; esac
LOGIN_OK 200 Post "http://169.254.169.254/latest/meta-data/": context deadline exceeded metadata webhook: blocked (no answer)

A webhook to any ordinary address works as usual. As a control, the instance's own validation also delivers a test notification and a certificate deployment to a local receiver.

Core dumps are off for every service, and SSH accepts keys only:

systemctl show -p LimitCORE --value certimate nginx certimate-firstboot | tr '\n' ' '; echo
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|x11forwarding) '
0 0 0 permitrootlogin no passwordauthentication no x11forwarding no
10

Use your own domain and certificate

Point a DNS name at the instance. An Elastic IP is a good idea. Then run:

sudo certimate-set-domain certs.example.com

nginx then answers to that name (and the instance's addresses), and the self-signed certificate is re-issued for it.

To serve a certificate from a certificate authority, replace /etc/ssl/certimate/certimate.crt (full chain) and /etc/ssl/certimate/certimate.key (mode 0600, root), then run sudo systemctl reload nginx. certimate-set-domain never overwrites a certificate that is not its own.

Certimate itself can issue that certificate. A workflow with a Local host deploy node can write it. The service runs as the unprivileged certimate user with a read-only system, so have it write under /var/lib/certimate. Then copy the files into place with a root cron job or systemd path unit that you control.

11

The administrator password and the PocketBase dashboard

You can change the administrator password at any time. The old password and every signed-in session stop working, and the new password is written to the credentials file:

sudo certimate-admin-password rotate

You can also change it in the console under Settings.

Certimate runs on PocketBase, whose own database administration dashboard is at /_/. The public address does not publish it (HTTP 403). Reach it through an SSH tunnel and sign in with the same email and password:

ssh -i <your-key.pem> -L 8443:127.0.0.1:443 ubuntu@<public-ip>

Then browse to https://localhost:8443/_/.

The dashboard edits the database directly. Change collection rules only if you understand the effect. For example, keep the users collection's Create rule set to superusers only, so that nobody can register an account.

12

Your data and backups

Certimate stores everything in /var/lib/certimate/pb_data: the administrator, credentials, workflows, run logs, issued certificates and their private keys. Only the certimate user can open this directory:

sudo stat -c '%a %U %n' /var/lib/certimate/pb_data
700 certimate /var/lib/certimate/pb_data

Back it up with the PocketBase dashboard (Settings > Backups, through the tunnel), or stop Certimate and copy the directory:

sudo systemctl stop certimate
sudo tar -C /var/lib/certimate -czf /root/certimate-backup-$(date +%F).tar.gz pb_data
sudo systemctl start certimate

Treat the backup like a password store: it contains provider credentials and private keys.

13

Licences and source

Certimate is licensed under the MIT licence. The image carries the licence, the build record, the Go module list and the console's package lock in /usr/share/doc/sac-certimate/:

ls /usr/share/doc/sac-certimate/ sed -n '1,4p' /usr/share/doc/sac-certimate/BUILD-INFO
BUILD-INFO LICENSE-certimate-MIT.txt SOURCE.txt go-version-m.txt go.mod go.sum registered-providers.txt tree-delta.txt ui-dist.manifest ui-package-lock.json Certimate 0.4.36 - sac rebuild of the pinned upstream tag source: https://github.com/certimate-go/certimate.git tag v0.4.36 commit 85626f68aef89f11cd41cdcc3cef37cbf4f38e37 (MIT) + pkg/sdk3rd-forked https://github.com/certimate-go/.deps-repo-go.git commit baf25c1f3c420522d0d2bd1b7b0ca0a097af9565 (build input only; nothing of it is linked) removed: internal/certmgmt/deployers/sp_cmcccloud_cdn.go internal/certmgmt/deployers/sp_cmcccloud_vlb.go internal/certacme/certifiers/sp_cmcccloud_dns.go (the China Mobile eCloud providers: their SDK forks carry no licence)
  • Source: github.com/certimate-go/certimate at tag v0.4.36. This binary is a SecureAnyCloud rebuild of the pinned upstream tag. The official 0.4.36 release is built with Go 1.25, which is out of support and carries unfixed standard-library advisories. SecureAnyCloud therefore builds the same tag with Go 1.27.3, with GOEXPERIMENT=nojsonv2, which keeps Go's classic JSON implementation that the bundled PocketBase 0.39.10 is written for. The build also raises golang.org/x/net to 0.62.0, golang.org/x/crypto to 0.58.0 and golang.org/x/image to 0.45.0, plus the minor x/sync, x/sys, x/term, x/text, x/mod and x/tools updates that go mod tidy makes. No Certimate source is patched.
  • China Mobile eCloud providers not included: the eCloud SDK sources that Certimate vendors carry no licence. The three files that register the China Mobile eCloud (cmcccloud) DNS and deployment providers are therefore left out of this build. Every other provider is unchanged (registered-providers.txt lists them).
  • Console: rebuilt at the tag with lodash and lodash-es 4.18.1 and js-cookie 3.0.8, which are fixed releases on the same major versions:
grep -cE 'gitlab.ecloud.com' /usr/share/doc/sac-certimate/go-version-m.txt || true
grep -E 'golang.org/x/(net|crypto|image)\s' /usr/share/doc/sac-certimate/go-version-m.txt | awk '{print $2, $3}' python3 -c 'import json; d=json.load(open("/usr/share/doc/sac-certimate/ui-package-lock.json"))["packages"]; [print(k.split("/")[-1], d[k]["version"]) for k in ("node_modules/lodash","node_modules/lodash-es","node_modules/js-cookie")]'
0 golang.org/x/crypto v0.58.0 golang.org/x/image v0.45.0 golang.org/x/net v0.62.0 lodash 4.18.1 lodash-es 4.18.1 js-cookie 3.0.8
14

Security notes

  • Keep port 22 open to your own networks only, and never open 8090. Consider limiting 443 to the networks you manage certificates from.
  • Certimate holds powerful credentials: DNS edit rights, SSH keys to your servers and cloud access keys. Give each one the narrowest scope that works, and protect the administrator password. Rotate it (Step 11) after sharing it.
  • The Local host deploy provider and the SSH deploy provider's pre and post commands run shell commands that you enter, on this instance as the certimate user or on your servers. Only administrators can configure them, so treat console access as shell access.
  • In production, use your own domain and a certificate from a certificate authority for the console.
  • Known residual advisory (reviewed for this release): golang.org/x/crypto/openpgp (GO-2026-5932) is unmaintained and has no fixed release. A dependency links it in, but Certimate's code never calls it, and source-level govulncheck reports no reachable path. govulncheck reports no other reachable advisory for this build.

Support

SecureAnyCloud provides 24/7 technical support for this image by email and live chat: sales@secureanycloud.com.

Certimate and PocketBase are trademarks of their respective owners. SecureAnyCloud is not affiliated with or endorsed by them.

Deploy on AWS

Launch Certimate 0.4.36 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS