Security AWS

Certimate 0.4.36 on AWS User Guide

| Product: Certimate 0.4.36 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Certimate 0.4.36 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg identity, secrets and PKI listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Certimate is an open source, self-hosted tool for the whole life of SSL/TLS certificates. You build workflows in a visual editor: 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, deploy it to the places that need it (servers over SSH, Kubernetes, CDNs, load balancers, cloud certificate stores, webhooks and many more), renew it on a schedule and send notifications. Provider credentials are kept in one place and reused by your workflows.

The cloudimg image is a hardened, ready-to-use Certimate server:

  • 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 generated on your instance's first boot (an end-entity certificate, not a certificate authority), and port 80 redirects to HTTPS. The instance's name is set from its own EC2 public DNS name at first boot; 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 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 only reachable 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 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, verified. The Certimate binary is a cloudimg 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.

What is 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 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: Certimate listens on the loopback address only.
  • For production, a DNS name you control (for example certs.example.com) and, ideally, a certificate from a certificate authority for the console itself (Certimate can issue it).

Step 1: Launch the instance

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

With the AWS CLI (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. If you want the instance to use your own name from the start, put it in /etc/cloudimg/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.

Step 2: Connect to your instance

Operating system SSH user
Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 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:

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":{}}

Step 4: Get your sign-in and the certificate fingerprint

sudo cat /root/certimate-credentials.txt

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

# 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 name, every address the instance answers to and the certificate fingerprint are also shown at login and by:

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 warns about it. Open the certificate details in the browser, check that its SHA-256 fingerprint matches certimate.tls.sha256_fingerprint, and 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).

Step 5: Sign in to the console

Browse to the certimate.url address:

The Certimate sign-in page with Username/Email and Password fields

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

The Certimate sign-in page showing "Failed to authenticate" after the default admin@certimate.fun / 1234567890 sign-in

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

The Certimate dashboard with the navigation menu: Dashboard, Workflows, Certificates, Credentials, Presets and Settings

Two sign-ins per three seconds are allowed from one client address; further attempts get "Too many requests" for a few seconds.

Step 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:

The Create Credential page listing providers: Local host, Remote host (SSH), Remote host (FTP), Webhook, Kubernetes, Object storage, Alibaba Cloud, Tencent Cloud, Huawei Cloud, Volcengine, AWS, Azure, GCP, OCI, Bunny, BytePlus, Cloudflare and more

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

The Cloudflare credential form asking for a Cloudflare API token and an optional Zone API token

Use credentials scoped to what Certimate must do (for a DNS-01 challenge: edit TXT records of the zones you issue for). They are stored in Certimate's 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 role 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".

Step 7: Build a workflow

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

The Certimate Workflows page

Issued certificates appear under Certificates, with their expiry dates, and can be downloaded or revoked there.

Step 8: Check the server from the command line

One command proves the whole chain on the instance: the console and health check answer over HTTPS, the default administrator is refused, the per-instance administrator signs in and reads a protected collection, a request without a token and a wrong password are refused, anonymous sign-up is refused, and 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

Step 9: Check the security posture

The certificate is an end-entity certificate for the instance's name and addresses (plus localhost for the tunnel), which 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 = cloudimg
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

TLS 1.0 and 1.1 are refused by the server, and a request for a host name 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 and never a client-supplied one:

grep -E 'proxy_set_header +X-(Real-IP|Forwarded-For)' /etc/nginx/cloudimg/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 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 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/cloudimg/certimate/cmapi.py login https://127.0.0.1 /root/certimate-credentials.txt $T/tok
R=$(sudo python3 -I /usr/local/lib/cloudimg/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; the instance's own validation also delivers a test notification and a certificate deployment to a local receiver as the control.

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

Step 10: Use your own domain and certificate

Point a DNS name at the instance (an Elastic IP is a good idea), then:

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) and 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, but the service runs as the unprivileged certimate user with a read-only system, so have it write under /var/lib/certimate and copy the files into place with a root cron job or systemd path unit you control.

Step 11: The administrator password and the PocketBase dashboard

Change the administrator password at any time (the old password and every signed-in session stop working; 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 /_/. It is not published on the public address (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 PocketBase superuser sign-in page reached through the SSH tunnel

The dashboard edits the database directly; change collection rules only if you know why (for example, keep the users collection's Create rule set to superusers only, so that nobody can register an account).

Step 12: Your data and backups

Everything Certimate stores (the administrator, credentials, workflows, run logs, issued certificates and their private keys) is in /var/lib/certimate/pb_data, a directory only the certimate user can open:

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.

Step 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/cloudimg-certimate/:

ls /usr/share/doc/cloudimg-certimate/
sed -n '1,4p' /usr/share/doc/cloudimg-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 - cloudimg 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 cloudimg 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, so cloudimg 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) and 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 go mod tidy makes). No Certimate source is patched.
  • China Mobile eCloud providers not included: the eCloud SDK sources Certimate vendors carry no licence, so the three files that register the China Mobile eCloud (cmcccloud) DNS and deployment providers are 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 (fixed releases on the same major versions):
grep -cE 'gitlab.ecloud.com' /usr/share/doc/cloudimg-certimate/go-version-m.txt || true
grep -E 'golang.org/x/(net|crypto|image)\s' /usr/share/doc/cloudimg-certimate/go-version-m.txt | awk '{print $2, $3}'
python3 -c 'import json; d=json.load(open("/usr/share/doc/cloudimg-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

Step 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, 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/post commands run shell commands you enter (on this instance as the certimate user, or on your servers). Only administrators can configure them; treat console access as shell access.
  • Use your own domain and a certificate from a certificate authority for the console in production.
  • Known residual advisory (reviewed for this release): golang.org/x/crypto/openpgp (GO-2026-5932, the package is unmaintained, no fixed release) is linked in through a dependency, but Certimate's code never calls it (source-level govulncheck reports no reachable path). govulncheck reports no other reachable advisory for this build.

Support

cloudimg provides 24/7 technical support for this image by email and live chat: support@cloudimg.co.uk. Certimate and PocketBase are trademarks of their respective owners; cloudimg is not affiliated with or endorsed by them.