I Tried Automating the Deployment of ACM Exportable Certificates with AWS Workload Credentials Provider
This page has been translated by machine translation. View original
Introduction
On June 11, 2026, AWS Workload Credentials Provider (hereafter abbreviated as "WCP") was announced.
ACM exportable public certificates have a validity period of 198 days, and ACM automatically renews them 45 days before expiration. However, deploying the renewed certificate to servers was the user's responsibility. Considering that the CA/B Forum's mandate to shorten certificate validity periods will increase renewal frequency, the burden of manual operations will grow.
The Certificate Management feature of WCP provides a mechanism to address this challenge.
| Item | Before | After adopting WCP |
|---|---|---|
| Certificate export | Manual or EventBridge + Lambda | WCP runs it automatically |
| File placement | Custom script | WCP writes the files |
| Server reload | Custom script | Runs automatically via refresh_command |
| Renewal check | Self-designed with EventBridge / cron, etc. | Automatically checked every 24 hours |
| Idempotency | Implemented on your own | Skipped if there is no difference |
In this article, I will configure WCP for Apache (httpd) and Nginx on EC2 and confirm that automatic certificate placement and server reload work.
Official documentation:
Verification Environment
| Item | Value |
|---|---|
| Region | ap-northeast-1 |
| EC2 instance | t3a.small / AL2023 |
| Web servers | httpd (Apache 2.4) on :443 / Nginx on :8443 |
| WCP | v3.0.0 |
| Domain | wcp-demo.example.com |
IAM configuration:
- IAM user
wcp-demo-user:sts:AssumeRoleonly (Access Key authentication assuming on-premises) - IAM role
wcp-demo-role:acm:ExportCertificate,acm:DescribeCertificate(limited to the certificate ARN. DescribeCertificate was also granted in this verification)
The environment credentials have only the AssumeRole permission, and ACM operation permissions are separated to the role side.
Prerequisites: Exportable ACM Certificate
Issuing an exportable ACM public certificate is a prerequisite. The main difference in the issuance operation is enabling "exportable" at request time. Note that exportable certificates incur charges of 7 USD for an FQDN certificate and 79 USD for a wildcard certificate at issuance and at renewal (as of June 2026).


For detailed issuance steps, refer to the following articles.
CLI issuance example (EC-prime256v1, same as in this verification):
aws acm request-certificate \
--domain-name wcp-demo.example.com \
--validation-method DNS \
--key-algorithm EC_prime256v1 \
--options ExportOption=ENABLED \
--region ap-northeast-1
Before: Manual Export + Deployment
Here are the steps before adopting WCP.
# パッケージインストール
sudo dnf install -y httpd mod_ssl nginx jq
# 証明書エクスポート(--passphrase は blob 型のため Base64 エンコード値を指定)
# temppass は検証用。本番では十分な強度のパスフレーズを使用する
aws acm export-certificate \
--certificate-arn arn:aws:acm:ap-northeast-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
--passphrase $(echo -n 'temppass' | base64) \
--region ap-northeast-1 \
--output json > cert-export.json
# JSON から各ファイルを抽出
jq -r '.Certificate' cert-export.json > certificate.pem
jq -r '.CertificateChain' cert-export.json > chain.pem
jq -r '.PrivateKey' cert-export.json > encrypted_key.pem
# 秘密鍵のパスフレーズ解除
openssl pkey -in encrypted_key.pem -out private.key -passin pass:temppass
# fullchain 作成・配置
cat certificate.pem chain.pem > fullchain.pem
sudo mkdir -p /etc/ssl/acm
sudo cp fullchain.pem /etc/ssl/acm/
sudo cp private.key /etc/ssl/acm/
sudo chmod 600 /etc/ssl/acm/private.key
# httpd SSL 設定
sudo sed -i 's|^SSLCertificateFile.*|SSLCertificateFile /etc/ssl/acm/fullchain.pem|' /etc/httpd/conf.d/ssl.conf
sudo sed -i 's|^SSLCertificateKeyFile.*|SSLCertificateKeyFile /etc/ssl/acm/private.key|' /etc/httpd/conf.d/ssl.conf
sudo systemctl start httpd && sudo systemctl enable httpd
# Nginx デフォルトポートを変更(httpd の 80 番と競合回避)
sudo sed -i 's/listen 80;/listen 8080;/' /etc/nginx/nginx.conf
sudo sed -i 's/listen \[::]:80;/listen [::]:8080;/' /etc/nginx/nginx.conf
# Nginx SSL 設定(8443 ポート、httpd と共存)
sudo tee /etc/nginx/conf.d/ssl.conf > /dev/null << 'EOF'
server {
listen 8443 ssl;
server_name wcp-demo.example.com;
ssl_certificate /etc/ssl/acm/fullchain.pem;
ssl_certificate_key /etc/ssl/acm/private.key;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
EOF
sudo systemctl start nginx && sudo systemctl enable nginx
# 疎通確認
curl -sk https://localhost # httpd (443)
curl -sk https://localhost:8443 # Nginx (8443)
# 一時ファイル削除(秘密情報を含むため)
rm -f cert-export.json certificate.pem chain.pem fullchain.pem encrypted_key.pem private.key
Repeating this procedure every few months becomes a heavier burden as the number of targets increases.
After: Automation with WCP
From here on, everything is run as the root user on EC2.
Creating the IAM User and Role
Create an IAM user for WCP's environment credentials and an IAM role with ACM export permissions.
# Create user
aws iam create-user --user-name wcp-demo-user
# Create role (AssumeRole allowed from the user)
aws iam create-role \
--role-name wcp-demo-role \
--assume-role-policy-document '{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Allow",
"Principal":{"AWS":"arn:aws:iam::123456789012:user/wcp-demo-user"},
"Action":"sts:AssumeRole"
}]
}'
# Grant ACM export permissions to the role
aws iam put-role-policy \
--role-name wcp-demo-role \
--policy-name acm-export-policy \
--policy-document '{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Allow",
"Action":["acm:ExportCertificate","acm:DescribeCertificate"],
"Resource":"arn:aws:acm:ap-northeast-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}]
}'
# Grant AssumeRole permission to the user
aws iam put-user-policy \
--user-name wcp-demo-user \
--policy-name assume-role-policy \
--policy-document '{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Allow",
"Action":"sts:AssumeRole",
"Resource":"arn:aws:iam::123456789012:role/wcp-demo-role"
}]
}'
# Issue Access Key (the output includes SecretAccessKey, so handle with care)
aws iam create-access-key --user-name wcp-demo-user
acm:GetCertificate was not granted in this configuration and it worked correctly. acm:DescribeCertificate is not listed in the README's Required permissions, but in this verification I confirmed behavior with it granted.
Building WCP
# Add swap
dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
# Install development tools and Rust
dnf -y groupinstall "Development Tools"
dnf -y install git
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
export PATH=/root/.cargo/bin:$PATH
# Get the source and build
git clone --branch v3.0.0 https://github.com/aws/aws-workload-credentials-provider.git /opt/aws-wcp
cd /opt/aws-wcp
cargo build --release
The build took about 10 minutes, and the generated binary is 27MB.
Because WCP is written in Rust, building from source requires a certain amount of memory. On a t3a.micro (2 vCPU / 1 GiB), cargo build failed due to insufficient memory. This time I added 2GB of swap to a t3a.small (2 vCPU / 2 GiB). I recommend not bringing the build toolchain into the runtime environment, and instead copying a binary built in a separate environment.
Placing the Binary and Creating a Dedicated User
# Place the binary
mkdir -p /opt/aws/workload-credentials-provider/bin
cp /opt/aws-wcp/target/release/aws-workload-credentials-provider \
/opt/aws/workload-credentials-provider/bin/
chmod +x /opt/aws/workload-credentials-provider/bin/aws-workload-credentials-provider
mkdir -p /opt/aws/workload-credentials-provider/logs
# Create dedicated user and group
useradd -r -s /sbin/nologin aws-wcp
groupadd -f awscreds
usermod -a -G awscreds aws-wcp
# Directory ownership
chown root:root /opt/aws/workload-credentials-provider
chmod 0755 /opt/aws/workload-credentials-provider
chown -R root:root /opt/aws/workload-credentials-provider/bin
chmod 0755 /opt/aws/workload-credentials-provider/bin/aws-workload-credentials-provider
chown -R aws-wcp:aws-wcp /opt/aws/workload-credentials-provider/logs
chmod 0750 /opt/aws/workload-credentials-provider/logs
# Certificate destination directory
mkdir -p /etc/ssl/acm
chown -R aws-wcp:aws-wcp /etc/ssl/acm
WCP runs as a dedicated system user aws-wcp (no login shell).
Creating config.toml
Place it at the default path /etc/aws-workload-credentials-provider/config.toml.
[logging]
log_level = "DEBUG"
log_to_file = true
[capabilities.secrets_manager]
enabled = false
[capabilities.acm]
enabled = true
[[capabilities.acm.certificates]]
certificate_arn = "arn:aws:acm:ap-northeast-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
certificate_path = "/etc/ssl/acm/fullchain.pem"
private_key_path = "/etc/ssl/acm/private.key"
role_arn = "arn:aws:iam::123456789012:role/wcp-demo-role"
refresh_command = "/usr/local/bin/reload-webservers.sh"
certificate_and_chain_permission = { mode = "0644" }
key_permission = { mode = "0600" }
mkdir -p /etc/aws-workload-credentials-provider
# (Save the above content as config.toml)
chown root:awscreds /etc/aws-workload-credentials-provider/config.toml
chmod 0640 /etc/aws-workload-credentials-provider/config.toml
Main configuration items:
| Item | Description |
|---|---|
certificate_arn |
ARN of the ACM certificate |
certificate_path |
Output destination for the certificate PEM (combined as fullchain when chain_path is omitted) |
private_key_path |
Output destination for the private key |
chain_path |
Chain certificate (when omitted, combined into certificate_path as fullchain) |
role_arn |
IAM role used for certificate export |
refresh_command |
Command to run after the certificate files are written |
This time I omitted chain_path and used fullchain mode. Both httpd and Nginx reference the same fullchain.pem.
In this verification I set log_level = "DEBUG", but in production, change it to INFO or similar and also consider log rotation. The log output destination is /opt/aws/workload-credentials-provider/logs.
Reload Script
cat > /usr/local/bin/reload-webservers.sh << 'EOF'
#!/bin/bash
set -euo pipefail
/bin/systemctl reload httpd
/usr/sbin/nginx -s reload
EOF
chown root:root /usr/local/bin/reload-webservers.sh
chmod 755 /usr/local/bin/reload-webservers.sh
Because WCP runs it as sudo -n <refresh_command>, sudo is not needed inside the script. Make the script root-owned and not writable by regular users. Shell syntax (pipes, &&, redirects, etc.) is not interpreted, so if multiple commands are needed, specify a wrapper script.
Running setup-config-based-permissions
/opt/aws/workload-credentials-provider/bin/aws-workload-credentials-provider \
setup-config-based-permissions \
--config /etc/aws-workload-credentials-provider/config.toml
This command automatically generates the following.
sudoers (/etc/sudoers.d/aws-workload-credentials-provider):
aws-wcp ALL=(ALL) NOPASSWD: /usr/local/bin/reload-webservers.sh
systemd drop-in (/etc/systemd/system/aws-workload-credentials-provider-acm.service.d/cert-paths.conf):
[Service]
ReadWriteDirectories=/etc/ssl/acm /opt/aws/workload-credentials-provider/logs
Since setup-config-based-permissions generates the sudoers and systemd drop-in from the contents of config.toml, it can reduce configuration drift at the time of generation. If you change config.toml, you need to run it again.
Note that setup-config-based-permissions is not explicitly described as a standalone step in the v3.0.0 README. This time I confirmed its behavior with --help and testing on a real machine, and used it for the purpose of incorporating only the permission settings into a manual configuration. The official main path is the install script.
systemd Service and Startup
cat > /etc/systemd/system/aws-workload-credentials-provider-acm.service << 'EOF'
[Unit]
Description=AWS Workload Credentials Provider - ACM
After=network-online.target
Wants=network-online.target
[Service]
User=aws-wcp
WorkingDirectory=/opt/aws/workload-credentials-provider
Type=simple
Restart=on-failure
RestartSec=5s
TimeoutSec=1min
ExecStart=/opt/aws/workload-credentials-provider/bin/aws-workload-credentials-provider acm start
[Install]
WantedBy=multi-user.target
EOF
# Set AWS credentials via drop-in
mkdir -p /etc/systemd/system/aws-workload-credentials-provider-acm.service.d
cat > /etc/systemd/system/aws-workload-credentials-provider-acm.service.d/creds.conf << 'EOF'
[Service]
Environment="AWS_ACCESS_KEY_ID=<ACCESS_KEY>"
Environment="AWS_SECRET_ACCESS_KEY=<SECRET_KEY>"
Environment="AWS_REGION=ap-northeast-1"
EOF
chown root:root /etc/systemd/system/aws-workload-credentials-provider-acm.service.d/creds.conf
chmod 0600 /etc/systemd/system/aws-workload-credentials-provider-acm.service.d/creds.conf
# Start
systemctl daemon-reload
systemctl enable --now aws-workload-credentials-provider-acm
It is started without the --config option. WCP reads /etc/aws-workload-credentials-provider/config.toml by default.
This time I used Access Key authentication because I assumed an on-premises environment, but for EC2 an instance profile is also an option, and for on-premises, IAM Roles Anywhere is also an option.
In this AL2023 environment, the sudo -n for refresh_command failed because of the hardening settings (such as NoNewPrivileges) in the unit generated by the official install script. For that reason I created the unit manually. I incorporated only the sudoers and ReadWriteDirectories settings via setup-config-based-permissions.
Also, on a clean install, httpd / Nginx cannot start unless the certificate files exist. On the other hand, running refresh_command (reload) while the web servers are not running will fail. This time, I introduced WCP from a state where httpd / Nginx were already running from the Before steps, so no problem occurred. When building in a clean environment, either place the certificate manually for the first time only, or consider a refresh_command that also accounts for the not-running case (such as reload-or-restart).
Verifying Operation
Log after startup (on success):
INFO ACM Scheduler starting with 1 certificate(s)
INFO Certificate task started for arn:aws:acm:...
INFO Starting certificate refresh
INFO Certificate files written
INFO Running refresh command: first cycle after provider startup
INFO Refresh command succeeded
INFO Certificate refresh successful
WCP operated in the following flow:
- Obtain temporary credentials via STS AssumeRole
- Export the certificate with ACM ExportCertificate
- Write the certificate files
- Run
refresh_command(httpd reload + Nginx reload)
I also confirmed that in the first cycle after a service restart, if there is no difference in the certificate, "Certificate files written" and "Running refresh command" are not output and the step is skipped (idempotent behavior).
Checking the certificate contents:
# File written by WCP
openssl x509 -in /etc/ssl/acm/fullchain.pem -noout -subject -issuer -serial -dates
# Certificates served by httpd / Nginx
openssl s_client -connect localhost:443 -servername wcp-demo.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -serial -dates
openssl s_client -connect localhost:8443 -servername wcp-demo.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -serial -dates
The subject / issuer / serial / dates of the leaf certificate in the file matched those of the leaf certificate served by the servers, confirming that placement and reload by WCP work. The private key is owned by aws-wcp with mode 0600, but since the root-privileged master process of httpd / Nginx reads the private key, no permission problems occur during reload.
Privilege path of refresh_command:
WCP (User=aws-wcp)
→ sudo -n /usr/local/bin/reload-webservers.sh
→ NOPASSWD allowed in /etc/sudoers.d/aws-workload-credentials-provider
→ run the reload script as root
→ httpd reload + nginx reload
Summary
Using the Certificate Management feature of AWS Workload Credentials Provider, I confirmed the initial retrieval of an ACM exportable certificate, file placement, web server reload, and skip behavior when there is no difference.
Previously, certificate export, removing the private key passphrase, file placement, and web server reload had to be implemented manually or with custom scripts. With WCP, after configuring the necessary IAM, systemd, and sudoers settings, you can automate these processes by defining the certificate ARN, output paths, and refresh_command in config.toml.
Note that this time I did not verify replacement across the actual ACM renewal date. The replacement of the certificate after renewal is a summary based on WCP's 24-hour periodic check specification.
In configurations that use ACM exportable certificates on web servers on EC2 or on-premises, WCP seems worth considering as an option to reduce deployment work at certificate renewal.
References
