A comprehensive Ansible collection for managing SSL certificates through the ZeroSSL API. This collection provides complete certificate lifecycle management including creation, validation, renewal, and deployment with enterprise-grade features.
- Complete Certificate Lifecycle: Create, validate, download, and renew SSL certificates
- Multiple Validation Methods: HTTP-01 and DNS-01 domain validation
- Multi-Domain Support: Single domain, multi-domain (SAN), and wildcard certificates
- Intelligent Caching: Reduce API calls with smart caching mechanisms
- Concurrent Operations: Thread-safe operations with proper locking
- Idempotent Behavior: Ansible-compliant operations with proper change detection
- Automatic Renewal: Check and renew certificates based on expiration thresholds
- Secure File Handling: Proper file permissions and atomic operations
- Comprehensive Error Handling: Retry logic and detailed error reporting
- Ansible: 8.0 or later
- Python: 3.12 or later
- ZeroSSL Account: API access key required
- Dependencies:
requests,cryptography,dnspython
ansible-galaxy collection install sillygod.zerosslansible-galaxy collection install git+https://github.com/sillygod/ansible-zerossl.git# Clone and build the collection
git clone https://github.com/sillygod/ansible-zerossl.git
cd ansible-zerossl
ansible-galaxy collection build .
ansible-galaxy collection install sillygod-zerossl-*.tar.gzansible-galaxy collection list sillygod.zerosslCreate a requirements.yml file for your project:
---
collections:
- name: sillygod.zerossl
version: ">=1.0.0"Then install with:
ansible-galaxy collection install -r requirements.yml---
- name: Create SSL certificate
sillygod.zerossl.zerossl_certificate:
api_key: "{{ zerossl_api_key }}"
domains:
- example.com
- www.example.com
state: present
certificate_path: /etc/ssl/certs/example.com.crt
private_key_path: /etc/ssl/private/example.com.key
validation_method: HTTP_CSR_HASH
web_root: /var/www/html# Store API key securely
ansible-vault create group_vars/all/vault.yml# vault.yml
vault_zerossl_api_key: your_actual_api_key_here# playbook.yml
---
- name: SSL Certificate Management
hosts: webservers
vars:
zerossl_api_key: "{{ vault_zerossl_api_key }}"
tasks:
- name: Ensure SSL certificate exists
sillygod.zerossl.zerossl_certificate:
api_key: "{{ zerossl_api_key }}"
domains: "{{ ssl_domains }}"
state: present
certificate_path: "{{ ssl_cert_path }}"
private_key_path: "{{ ssl_key_path }}"| Parameter | Type | Description |
|---|---|---|
api_key |
string | ZeroSSL API access key |
domains |
list | List of domains for the certificate |
| Parameter | Type | Default | Description |
|---|---|---|---|
state |
string | present |
Desired certificate state |
validation_method |
string | HTTP_CSR_HASH |
Domain validation method |
certificate_path |
path | - | Path to save certificate file |
private_key_path |
path | - | Path to save private key file |
ca_bundle_path |
path | - | Path to save CA bundle file |
full_chain_path |
path | - | Path to save full certificate chain |
validity_days |
int | 90 |
Certificate validity period |
renew_threshold_days |
int | 30 |
Days before expiration to renew |
timeout |
int | 30 |
API request timeout in seconds |
force |
bool | false |
Force certificate renewal |
present: Ensure certificate exists and is valid (default)request: Create certificate request and return validation challengesvalidate: Validate a pending certificatedownload: Download an issued certificateabsent: Cancel/remove certificatecheck_renew_or_create: Check if certificate needs renewal
- name: Create single domain certificate
sillygod.zerossl.zerossl_certificate:
api_key: "{{ zerossl_api_key }}"
domains:
- example.com
state: present
validation_method: HTTP_CSR_HASH
certificate_path: /etc/ssl/certs/example.com.crt
private_key_path: /etc/ssl/private/example.com.key
web_root: /var/www/html- name: Create multi-domain certificate
sillygod.zerossl.zerossl_certificate:
api_key: "{{ zerossl_api_key }}"
domains:
- example.com
- www.example.com
- api.example.com
- blog.example.com
state: present
validation_method: HTTP_CSR_HASH
certificate_path: /etc/ssl/certs/example.com.crt
private_key_path: /etc/ssl/private/example.com.key
ca_bundle_path: /etc/ssl/certs/example.com-ca.crt
full_chain_path: /etc/ssl/certs/example.com-fullchain.crt
web_root: /var/www/html- name: Create wildcard certificate
sillygod.zerossl.zerossl_certificate:
api_key: "{{ zerossl_api_key }}"
domains:
- "*.example.com"
state: present
validation_method: DNS_CSR_HASH
certificate_path: /etc/ssl/certs/wildcard.example.com.crt
private_key_path: /etc/ssl/private/wildcard.example.com.key
register: cert_result
- name: Display DNS records for validation
debug:
msg: "Add DNS CNAME record: {{ item.cname_validation_p1 }} = {{ item.cname_validation_p2 }}"
loop: "{{ cert_result.dns_records }}"
when: cert_result.dns_records is defined# Step 1: Request certificate
- name: Request certificate
sillygod.zerossl.zerossl_certificate:
api_key: "{{ zerossl_api_key }}"
domains:
- example.com
state: request
validation_method: HTTP_CSR_HASH
register: cert_request
# Step 2: Place validation files (manual or automated)
- name: Place validation files
copy:
content: "{{ item.content }}"
dest: "{{ web_root }}/{{ item.url_path }}"
mode: '0644'
loop: "{{ cert_request.validation_files }}"
# Step 3: Validate certificate
- name: Validate certificate
sillygod.zerossl.zerossl_certificate:
api_key: "{{ zerossl_api_key }}"
certificate_id: "{{ cert_request.certificate_id }}"
state: validate
validation_method: HTTP_CSR_HASH
# Step 4: Download certificate
- name: Download certificate
sillygod.zerossl.zerossl_certificate:
api_key: "{{ zerossl_api_key }}"
certificate_id: "{{ cert_request.certificate_id }}"
state: download
certificate_path: /etc/ssl/certs/example.com.crt
private_key_path: /etc/ssl/private/example.com.key- name: Check if certificate needs renewal
sillygod.zerossl.zerossl_certificate:
api_key: "{{ zerossl_api_key }}"
domains:
- example.com
state: check_renew_or_create
renew_threshold_days: 30
register: renewal_check
- name: Renew certificate if needed
sillygod.zerossl.zerossl_certificate:
api_key: "{{ zerossl_api_key }}"
domains:
- example.com
state: present
force: true
when: renewal_check.needs_renewal- name: Advanced certificate configuration
sillygod.zerossl.zerossl_certificate:
api_key: "{{ zerossl_api_key }}"
domains: "{{ app_domains }}"
state: present
validation_method: HTTP_CSR_HASH
certificate_path: "{{ ssl_dir }}/{{ app_name }}.crt"
private_key_path: "{{ ssl_dir }}/{{ app_name }}.key"
ca_bundle_path: "{{ ssl_dir }}/{{ app_name }}-ca.crt"
full_chain_path: "{{ ssl_dir }}/{{ app_name }}-fullchain.crt"
validity_days: 90
renew_threshold_days: 14
timeout: 60
file_mode: '0644'
private_key_mode: '0600'
web_root: "{{ web_document_root }}"
enable_caching: true
notify:
- restart nginx
- reload ssl certificatesThe plugin returns comprehensive information about the certificate operation:
{
"changed": true,
"certificate_id": "cert-123456789",
"status": "issued",
"domains": ["example.com", "www.example.com"],
"expires": "2025-04-15 10:30:00",
"validation_method": "HTTP_CSR_HASH",
"files_created": [
"/etc/ssl/certs/example.com.crt",
"/etc/ssl/private/example.com.key"
],
"validation_files": [
{
"domain": "example.com",
"url_path": "/.well-known/pki-validation/validation-file.txt",
"content": "validation-content-hash"
}
],
"dns_records": [
{
"cname_validation_p1": "A1B2C3D4E5F6.example.com",
"cname_validation_p2": "A1B2C3D4E5F6.B2C3D4E5F6A1.C3D4E5F6A1B2.zerossl.com"
}
],
"msg": "Certificate created successfully"
}The plugin provides detailed error information for troubleshooting:
{
"failed": true,
"msg": "Certificate validation failed",
"error": {
"type": "ZeroSSLValidationError",
"message": "Domain validation failed for example.com",
"details": {
"domain": "example.com",
"validation_method": "HTTP_CSR_HASH",
"error_code": 10301
},
"retryable": true,
"retry_after": 300
}
}-
Use Ansible Vault for API keys:
ansible-vault encrypt_string 'your_api_key' --name 'zerossl_api_key'
-
Set proper file permissions:
private_key_mode: '0600' # Private keys file_mode: '0644' # Certificates
-
Use secure directories:
certificate_path: /etc/ssl/certs/example.com.crt private_key_path: /etc/ssl/private/example.com.key
-
Enable caching for repeated operations:
enable_caching: true
-
Use appropriate timeouts:
timeout: 60 # For slow networks
-
Batch operations when possible:
domains: - example.com - www.example.com - api.example.com # Single certificate for multiple domains
-
Set appropriate renewal thresholds:
renew_threshold_days: 30 # Renew 30 days before expiration
-
Use check mode for testing:
ansible-playbook ssl-playbook.yml --check
-
Monitor certificate expiration:
- name: Check certificate status sillygod.zerossl.zerossl_certificate: api_key: "{{ zerossl_api_key }}" domains: "{{ ssl_domains }}" state: check_renew_or_create register: cert_status
-
Domain validation failures:
- Ensure validation files are accessible via HTTP
- Check firewall and DNS settings
- Verify web server configuration
-
DNS validation issues:
- Verify DNS CNAME records are properly set with correct format (cname_validation_p1 → cname_validation_p2)
- Allow time for DNS propagation
- Check TTL values
-
File permission errors:
- Ensure Ansible has write access to target directories
- Check parent directory permissions
- Verify disk space availability
-
API rate limiting:
- Implement retry logic in playbooks
- Use caching to reduce API calls
- Monitor rate limit headers
Enable verbose output for troubleshooting:
ansible-playbook ssl-playbook.yml -vvvTest validation file accessibility:
curl -I http://example.com/.well-known/pki-validation/validation-file.txtCheck DNS CNAME records:
dig CNAME A1B2C3D4E5F6.example.com- sillygod.zerossl.zerossl_certificate: Main module for SSL certificate management
- Action Plugin:
sillygod.zerossl.zerossl_certificate - Module Utils: ZeroSSL API client and certificate management utilities
- Namespace:
community - Collection Name:
zerossl - Version:
1.0.0 - Minimum Ansible Version:
8.0.0
For detailed API documentation, see the ZeroSSL API Documentation.
This project implements a comprehensive three-tier testing approach:
tests/
├── unit/ # Unit tests - fast, isolated, mocked dependencies
├── component/ # Component tests - workflow testing with mocked APIs
├── integration/ # Integration tests - real ZeroSSL API calls ⚠️
├── fixtures/ # Test data and helpers
├── security/ # Security-focused tests
├── performance/ # Performance and load tests
└── compatibility/ # Ansible version compatibility tests
| Type | Speed | API Calls | When to Run | Purpose |
|---|---|---|---|---|
| Unit | Very Fast (<1s) | None (mocked) | Always | Test individual components |
| Component | Fast (1-5s) | None (mocked) | Pre-commit, CI | Test workflow integration |
| Integration | Slow (30s+) | Real API calls | Manual, releases | Test real API integration |
# Development workflow (fast feedback)
pytest tests/unit/ -x
# Pre-commit checks (comprehensive but fast)
pytest tests/unit/ tests/component/ --cov=plugins/
# Pre-release validation (manual, uses API quota)
pytest tests/integration/ -v -s
# Full test suite (development only)
pytest --cov=plugins/ --cov-report=htmlFor detailed testing documentation, see tests/README.md.
- Fork the repository at sillygod/ansible-zerossl
- Create a feature branch
- Make your changes
- Add tests for new functionality (unit and component tests required)
- Ensure fast tests pass:
pytest tests/unit/ tests/component/ - Submit a pull request
This project uses uv for fast, reliable dependency management.
# Clone the repository
git clone https://github.com/sillygod/ansible-zerossl.git
cd ansible-zerossl
# Run the automated setup script (installs uv if needed)
./dev-setup.sh
# Or manually with uv
uv sync --all-extras
source .venv/bin/activateKey Benefits of uv:
- ⚡ 10-100x faster than pip
- 🔒 Automatic lock file management
- 📦 Better dependency resolution
If you want to customize the ansible.cfg for your own settings, you can run git update-index --skip-worktree ansible.cfg to ignore changes.
# Add a new dependency
uv add requests
# Add a dev dependency
uv add --dev pytest
# Update all dependencies
uv sync
# Export for CI/CD (auto-generated by pre-commit)
uv export --no-hashes --all-extras -o requirements-dev.txtThe project uses a three-tier testing strategy:
# Fast tests (CI-safe) - Unit and component tests with mocks
pytest tests/unit/ tests/component/ -v
# Run all tests except integration (recommended for development)
pytest -m "not integration" -v
# Run specific test categories
pytest tests/unit/ -v # Unit tests - isolated components
pytest tests/component/ -v # Component tests - workflow with mocks
pytest tests/integration/ -v # Integration tests - real ZeroSSL API ⚠️
pytest tests/performance/ -v # Performance and load tests
pytest tests/security/ -v # Security-focused tests
# Run with coverage (fast tests only)
pytest tests/unit/ tests/component/ --cov=plugins/ --cov-report=htmlIntegration tests make real API calls to ZeroSSL and require environment setup:
# Set up environment for integration tests
export ZEROSSL_API_KEY="your_actual_api_key"
export ZEROSSL_TEST_DOMAINS="test.yourdomain.com,api.yourdomain.com"
# Run integration tests (uses real API quota)
pytest tests/integration/ -v -s
# Skip integration tests (default behavior)
pytest -m "not integration" -v# Build collection artifact
ansible-galaxy collection build
# Install locally for testing
ansible-galaxy collection install sillygod-zerossl-*.tar.gz --forceThis project is licensed under the MIT License - see the LICENSE file for details.
- Issues: GitHub Issues
- Community: Ansible Community Forum
- Galaxy: Ansible Galaxy Page
See CHANGELOG.md for version history and release notes.
- ZeroSSL for providing the SSL certificate API
- Ansible for the automation framework
- The open-source community for contributions and feedback
- Spec kit
This repo uses spec kits to do major refactor and tests for the original code base.