In modern IT infrastructure management, secure management of SSL certificates has become increasingly important. As the number of services grows and certificate renewal cycles shorten, manual certificate management becomes inefficient and error-prone. This article details how to use Ansible for automated batch deployment and renewal of certificates, improving management efficiency and reducing security risks.
The Importance of SSL Certificate Management
SSL certificates are the foundation of network communication security. They ensure:
- Data Encryption: Prevents sensitive data from being intercepted during transmission
- Identity Verification: Ensures users connect to the genuine server, not a man-in-the-middle attack
- Trust Establishment: Builds user trust through trusted Certificate Authorities (CAs)
- Compliance Requirements: Meets various regulations and standards for data protection
Common challenges with manual certificate management:
- Delayed expiration alerts: Easy to miss certificate renewal dates, leading to service outages
- Low batch deployment efficiency: Manually configuring certificates on each server is time-consuming
- Configuration inconsistency: Certificate configurations may differ across servers
- High error risk: Manual operations are error-prone and may cause service unavailability
Ansible Automation Management Architecture
As a powerful automation operations tool, Ansible’s advantages include:
- Agentless architecture: No client installation required on target nodes
- Simple and easy to use: Uses YAML for configuration files, easy to understand and maintain
- Modular design: Rich module library supporting various operations tasks
- Idempotency: Safe to execute repeatedly, ensuring consistent system state
Basic Architecture Design
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| .
├── files/ # Certificate file storage directory
│ ├── domain1.com.crt # Certificate file
│ ├── domain1.com.key # Private key file
│ └── ...
├── handlers/ # Handler files
│ └── main.yaml # Service restart handler
├── tasks/ # Task files
│ └── main.yml # Main task definitions
├── templates/ # Template files
│ ├── nginx.conf.j2 # Nginx configuration template
│ └── ...
├── vars/ # Variable files
│ └── main.yml # Configuration variables
└── README.md # Project documentation
|
Complete Implementation Solution
1. Directory Structure Creation
First, create the basic directory structure:
1
2
| mkdir -p ssl-certs/ansible/{files,handlers,tasks,templates,vars}
cd ssl-certs/ansible
|
2. Variable Configuration File
Create the vars/main.yml file to define the basic certificate management configuration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| # coding: utf-8
---
# SSL certificate management configuration
# Client certificate configuration
ssl_certificates:
"example-client":
server_ip: ["203.0.113.10", "203.0.113.11"]
ssl_cer_name: "mail.example.com"
domain_name: "example.com"
server_name: "mail.example.com"
ssl_force: true
ssl_redirect: true
"another-client":
server_ip: ["198.51.100.20", "198.51.100.21"]
ssl_cer_name: "secure.example.org"
domain_name: "example.org"
server_name: "mail.example.org"
ssl_force: false
ssl_redirect: false
# Multi-domain certificate configuration
multi_domain_ssl:
# Domains requiring forced HTTPS
force_ssl_domain: [
"secure.example.com",
"api.example.com",
"admin.example.com"
]
# Standard HTTPS domains
standard_ssl_domain: [
"shop.example.com",
"blog.example.com"
]
# SSL certificate configuration
ssl_config:
ssl_certificate_path: "/etc/ssl/certs"
ssl_private_key_path: "/etc/ssl/private"
ssl_dhparam_path: "/etc/ssl/certs/dhparam.pem"
ssl_session_timeout: "5m"
ssl_protocols: "TLSv1.2 TLSv1.3"
ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
# Nginx configuration
nginx_config:
server_port: 80
ssl_server_port: 443
worker_processes: "auto"
worker_connections: 1024
keepalive_timeout: "65"
gzip: true
|
3. Task Definition File
Create the tasks/main.yml file to define the main certificate management tasks:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
| ---
- name: SSL certificate management tasks
hosts: ssl_servers
become: yes
vars_files:
- vars/main.yml
tasks:
# 1. Ensure certificate directories exist
- name: Create certificate directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- "{{ ssl_config.ssl_certificate_path }}"
- "{{ ssl_config.ssl_private_key_path }}"
- "{{ ssl_config.ssl_dhparam_path | dirname }}"
# 2. Copy certificate files
- name: Copy SSL certificate files
copy:
src: "files/{{ item.ssl_cer_name }}.crt"
dest: "{{ ssl_config.ssl_certificate_path }}/{{ item.ssl_cer_name }}.crt"
mode: '0644'
owner: root
group: root
loop: "{{ ssl_certificates.values() }}"
notify: restart nginx
# 3. Copy private key files
- name: Copy SSL private key files
copy:
src: "files/{{ item.ssl_cer_name }}.key"
dest: "{{ ssl_config.ssl_private_key_path }}/{{ item.ssl_cer_name }}.key"
mode: '0600'
owner: root
group: root
loop: "{{ ssl_certificates.values() }}"
notify: restart nginx
# 4. Generate DH parameters
- name: Generate DH parameters
command: openssl dhparam -out {{ ssl_config.ssl_dhparam_path }} 2048
args:
creates: "{{ ssl_config.ssl_dhparam_path }}"
notify: restart nginx
# 5. Generate Nginx configuration
- name: Generate Nginx configuration file
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: '0644'
owner: root
group: root
notify: restart nginx
# 6. Generate site configuration
- name: Generate site configuration files
template:
src: site_config.j2
dest: "/etc/nginx/conf.d/{{ item.domain_name }}.conf"
mode: '0644'
owner: root
group: root
loop: "{{ ssl_certificates.values() }}"
notify: restart nginx
# 7. Set SELinux (if enabled)
- name: Set SELinux context
sefcontext:
target: "{{ ssl_config.ssl_certificate_path }}/{{ item.ssl_cer_name }}.*"
setype: httpd_sys_content_t
recurse: yes
loop: "{{ ssl_certificates.values() }}"
when: ansible_selinux.status == 'enabled'
# 8. Verify SSL certificates
- name: Verify SSL certificates
command: openssl x509 -noout -text -in "{{ ssl_config.ssl_certificate_path }}/{{ item.ssl_cer_name }}.crt"
loop: "{{ ssl_certificates.values() }}"
register: cert_check
# 9. Check certificate expiration
- name: Check certificate expiration
command: openssl x509 -enddate -noout -in "{{ ssl_config.ssl_certificate_path }}/{{ item.ssl_cer_name }}.crt"
loop: "{{ ssl_certificates.values() }}"
register: cert_expiry
# 10. Output certificate information
- name: Output certificate information
debug:
msg: "Certificate {{ item.ssl_cer_name }} expires: {{ item.stdout }}"
loop: "{{ cert_expiry.results }}"
loop_control:
label: "{{ item.item.ssl_cer_name }}"
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
listen: restart nginx
- name: SSL certificate report generation
hosts: localhost
gather_facts: no
tasks:
- name: Generate SSL certificate report
template:
src: ssl_report.j2
dest: "/tmp/ssl_certificate_report.html"
delegate_to: localhost
|
4. Nginx Configuration Template
Create the templates/nginx.conf.j2 file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| user nginx;
worker_processes {{ nginx_config.worker_processes }};
worker_rlimit_nofile 100000;
events {
worker_connections {{ nginx_config.worker_connections }};
multi_accept on;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Log format
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
# Basic settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout {{ nginx_config.keepalive_timeout }};
types_hash_max_size 2048;
server_tokens off;
# SSL settings
ssl_session_cache shared:SSL:10m;
ssl_session_timeout {{ ssl_config.ssl_session_timeout }};
ssl_protocols {{ ssl_config.ssl_protocols }};
ssl_ciphers '{{ ssl_config.ssl_ciphers }}';
ssl_prefer_server_ciphers on;
ssl_dhparam {{ ssl_config.ssl_dhparam_path }};
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Gzip compression settings
{{- if nginx_config.gzip }}
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private must-revalidate max-age-0 auth;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
{{- endif }}
# Include site configurations
include /etc/nginx/conf.d/*.conf;
}
|
5. Site Configuration Template
Create the templates/site_config.j2 file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| server {
listen {{ nginx_config.server_port }};
listen {{ nginx_config.ssl_server_port }} ssl;
# Basic settings
server_name {{ item.domain_name }};
root /var/www/{{ item.domain_name }};
index index.html index.htm;
# SSL configuration
ssl_certificate {{ ssl_config.ssl_certificate_path }}/{{ item.ssl_cer_name }}.crt;
ssl_certificate_key {{ ssl_config.ssl_private_key_path }}/{{ item.ssl_cer_name }}.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
# Redirect configuration
{{- if item.ssl_redirect and item.ssl_force }}
if ($scheme != "https") {
return 301 https://$host$request_uri;
}
{{- endif }}
# Log configuration
access_log /var/log/nginx/{{ item.domain_name }}_access.log;
error_log /var/log/nginx/{{ item.domain_name }}_error.log;
# Web root directory permissions
location / {
try_files $uri $uri/ =404;
}
# Static file caching
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Security settings
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Error pages
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# PHP support (if needed)
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
|
6. Handler Configuration
Create the handlers/main.yaml file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| ---
- name: restart nginx
service:
name: nginx
state: restarted
enabled: yes
- name: reload nginx
service:
name: nginx
state: reloaded
enabled: yes
- name: start nginx
service:
name: nginx
state: started
enabled: yes
- name: stop nginx
service:
name: nginx
state: stopped
|
7. SSL Report Template
Create the templates/ssl_report.j2 file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSL Certificate Management Report</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.cert-card {
border: 1px solid #ddd;
border-radius: 8px;
margin-bottom: 20px;
overflow: hidden;
}
.cert-header {
background-color: #007bff;
color: white;
padding: 15px;
font-weight: bold;
}
.cert-body {
padding: 15px;
}
.cert-info {
margin-bottom: 10px;
}
.cert-info label {
font-weight: bold;
color: #555;
}
.status {
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.status.valid {
background-color: #d4edda;
color: #155724;
}
.status.expired {
background-color: #f8d7da;
color: #721c24;
}
.status.expiring {
background-color: #fff3cd;
color: #856404;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}
th {
background-color: #f2f2f2;
font-weight: bold;
}
.summary {
background-color: #e9ecef;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>SSL Certificate Management Report</h1>
<div class="summary">
<h2>Report Summary</h2>
<p>Generated: {{ ansible_date_time.date }} {{ ansible_date_time.time }}</p>
<p>Total Certificates: {{ ssl_certificates | length }}</p>
<p>Valid Certificates: {{ valid_certs }}</p>
<p>Expired Certificates: {{ expired_certs }}</p>
<p>Expiring Soon: {{ expiring_certs }}</p>
</div>
<h2>Certificate Details</h2>
{% for cert_name, cert_info in ssl_certificates.items() %}
<div class="cert-card">
<div class="cert-header">
{{ cert_info.domain_name }} - {{ cert_info.ssl_cer_name }}
</div>
<div class="cert-body">
<div class="cert-info">
<label>Status:</label>
<span class="status {{ cert_info.status }}">{{ cert_info.status_text }}</span>
</div>
<div class="cert-info">
<label>IP Address:</label>
{{ cert_info.server_ip | join(', ') }}
</div>
<div class="cert-info">
<label>Domain:</label>
{{ cert_info.domain_name }}
</div>
<div class="cert-info">
<label>Server Name:</label>
{{ cert_info.server_name }}
</div>
<div class="cert-info">
<label>Force HTTPS:</label>
{{ cert_info.ssl_force | string | lower }}
</div>
<div class="cert-info">
<label>Expiration Date:</label>
{{ cert_info.expiry_date }}
</div>
<div class="cert-info">
<label>Days Remaining:</label>
{{ cert_info.days_remaining }}
</div>
</div>
</div>
{% endfor %}
<h2>Configuration Details</h2>
<table>
<thead>
<tr>
<th>Setting</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Certificate Path</td>
<td>{{ ssl_config.ssl_certificate_path }}</td>
</tr>
<tr>
<td>Private Key Path</td>
<td>{{ ssl_config.ssl_private_key_path }}</td>
</tr>
<tr>
<td>DH Parameters Path</td>
<td>{{ ssl_config.ssl_dhparam_path }}</td>
</tr>
<tr>
<td>SSL Protocols</td>
<td>{{ ssl_config.ssl_protocols }}</td>
</tr>
<tr>
<td>Cipher Suites</td>
<td>{{ ssl_config.ssl_ciphers }}</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
|
Certificate Verification and Checking
Local Certificate Content Verification
Before deploying certificates, it’s recommended to verify their content locally:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # View certificate details
openssl x509 -noout -text -in mail.example.com.crt
# Verify certificate format
openssl x509 -in mail.example.com.crt -text -noout | grep "Subject:"
# Verify certificate validity period
openssl x509 -in mail.example.com.crt -noout -dates
# Verify certificate chain integrity
openssl verify -CAfile ca-bundle.crt mail.example.com.crt
# Check certificate private key match
openssl pkey -in mail.example.com.key -pubout | openssl dgst -sha256 -verify <(openssl x509 -in mail.example.com.crt -pubkey -noout) -signature <(openssl x509 -in mail.example.com.crt -noout -signer mail.example.com.crt)
|
Server Connection Certificate Verification
After deployment, verify certificates are working properly:
1
2
3
4
5
6
7
8
9
10
11
| # Connect directly to SSL port for verification
openssl s_client -connect mail.example.com:443
# Verify certificate chain
openssl s_client -connect mail.example.com:443 -showcerts
# Check SSL/TLS protocol support
openssl s_client -connect mail.example.com:443 -tls1_2
# Test SSL handshake performance
openssl s_client -connect mail.example.com:443 -msg
|
Ansible Playbook Usage Examples
Basic Deployment Playbook
Create the deploy_ssl.yml file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| ---
- name: SSL certificate batch deployment
hosts: ssl_servers
become: yes
vars_files:
- vars/main.yml
tasks:
- name: Create certificate directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- "{{ ssl_config.ssl_certificate_path }}"
- "{{ ssl_config.ssl_private_key_path }}"
- name: Copy certificate files
copy:
src: "files/{{ item.ssl_cer_name }}.crt"
dest: "{{ ssl_config.ssl_certificate_path }}/{{ item.ssl_cer_name }}.crt"
mode: '0644'
owner: root
group: root
loop: "{{ ssl_certificates.values() }}"
notify: restart nginx
- name: Copy private key files
copy:
src: "files/{{ item.ssl_cer_name }}.key"
dest: "{{ ssl_config.ssl_private_key_path }}/{{ item.ssl_cer_name }}.key"
mode: '0600'
owner: root
group: root
loop: "{{ ssl_certificates.values() }}"
notify: restart nginx
- name: Generate site configuration
template:
src: site_config.j2
dest: "/etc/nginx/conf.d/{{ item.domain_name }}.conf"
mode: '0644'
owner: root
group: root
loop: "{{ ssl_certificates.values() }}"
notify: restart nginx
- name: Verify SSL configuration
command: nginx -t
notify: restart nginx
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
|
Certificate Check Playbook
Create the check_ssl.yml file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| ---
- name: SSL certificate check
hosts: ssl_servers
become: yes
vars_files:
- vars/main.yml
tasks:
- name: Check certificate file existence
stat:
path: "{{ ssl_config.ssl_certificate_path }}/{{ item.ssl_cer_name }}.crt"
register: cert_stat
loop: "{{ ssl_certificates.values() }}"
- name: Check certificate validity period
command: openssl x509 -enddate -noout -in "{{ ssl_config.ssl_certificate_path }}/{{ item.ssl_cer_name }}.crt"
loop: "{{ ssl_certificates.values() }}"
register: cert_expiry
when: item.stat.exists is defined and item.stat.exists
- name: Calculate remaining days
command: >
openssl x509 -enddate -noout -in "{{ ssl_config.ssl_certificate_path }}/{{ item.ssl_cer_name }}.crt"
| awk -F= '/notAfter/ {print $2; system("date -d \""$2\" +%s")}'
| tail -1 | xargs -I {} expr $(( ({} - $(date +%s)) / 86400 ))
loop: "{{ ssl_certificates.values() }}"
register: days_remaining
when: item.stat.exists is defined and item.stat.exists
- name: Generate certificate report
template:
src: ssl_report.j2
dest: "/tmp/ssl_certificate_report_{{ item.domain_name }}.html"
loop: "{{ ssl_certificates.values() }}"
when: item.stat.exists is defined and item.stat.exists
|
Automated Deployment Workflow
1. Certificate Preparation Phase
1
2
3
4
5
6
7
8
9
10
11
| # Generate private key
openssl genrsa -out mail.example.com.key 2048
# Generate Certificate Signing Request (CSR)
openssl req -new -key mail.example.com.key -out mail.example.com.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=Example Company/CN=mail.example.com"
# Sign certificate with CA
openssl x509 -req -in mail.example.com.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out mail.example.com.crt -days 365
# Or use Let's Encrypt to obtain a certificate
certbot certonly --standalone -d mail.example.com --email [email protected] --agree-tos
|
2. Ansible Inventory Configuration
Create the inventory file:
1
2
3
4
5
6
7
| [ssl_servers]
web-server-1 ansible_host=203.0.113.10
web-server-2 ansible_host=203.0.113.11
[ssl_servers:vars]
ansible_ssh_user=admin
ansible_ssh_private_key_file=~/.ssh/id_rsa
|
3. Execute Deployment
1
2
3
4
5
6
7
8
| # Execute SSL certificate deployment
ansible-playbook -i inventory deploy_ssl.yml
# Execute certificate check
ansible-playbook -i inventory check_ssl.yml
# Check certificates on specific servers only
ansible-playbook -i inventory --limit web-server-1 check_ssl.yml
|
Monitoring and Alerting
Certificate Expiration Monitoring
Create the monitor_ssl_expiry.yml file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| ---
- name: Monitor SSL certificate expiration
hosts: ssl_servers
become: yes
vars_files:
- vars/main.yml
tasks:
- name: Get certificate expiration dates
command: openssl x509 -enddate -noout -in "{{ ssl_config.ssl_certificate_path }}/{{ item.ssl_cer_name }}.crt"
loop: "{{ ssl_certificates.values() }}"
register: cert_expiry
- name: Check if certificates are expiring soon
debug:
msg: "Certificate {{ item.item.ssl_cer_name }} is expiring on {{ item.stdout }}, please renew promptly!"
loop: "{{ cert_expiry.results }}"
loop_control:
label: "{{ item.item.ssl_cer_name }}"
when: >
(ansible_date_time.epoch | int + 2592000) > (item.stdout | regex_findall('notAfter=(.+)') | first | string | to_datetime('%b %d %H:%M:%S %Y %Z') | timestamp)
|
Auto-Renewal Script
Create the auto_renew_ssl.sh script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| #!/bin/bash
# SSL certificate auto-renewal script
LOG_FILE="/var/log/ssl_renewal.log"
NOTIFICATION_EMAIL="[email protected]"
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
check_certificate_expiry() {
local cert_file=$1
local domain_name=$2
local expiry_days=$(openssl x509 -enddate -noout -in "$cert_file" | awk -F= '/notAfter/ {print $2; system("date -d \""$2\" +%s")}' | tail -1 | xargs -I {} expr $(( ({} - $(date +%s)) / 86400 ))
echo "$expiry_days"
}
renew_certificate() {
local domain_name=$1
log_message "Starting certificate renewal: $domain_name"
# Renew using Let's Encrypt
certbot renew --cert-name "$domain_name" --quiet
if [ $? -eq 0 ]; then
log_message "Certificate $domain_name renewed successfully"
# Restart nginx to load new certificate
systemctl restart nginx
log_message "Nginx service restarted"
else
log_message "Certificate $domain_name renewal failed"
# Send alert email
echo "Certificate $domain_name renewal failed, please check immediately" | mail -s "SSL Certificate Renewal Failure Alert" "$NOTIFICATION_EMAIL"
fi
}
# Check all certificates
for cert in /etc/letsencrypt/live/*/cert.pem; do
if [ -f "$cert" ]; then
domain_name=$(basename $(dirname "$cert"))
expiry_days=$(check_certificate_expiry "$cert" "$domain_name")
log_message "Domain: $domain_name, Days remaining: $expiry_days"
# Renew if less than 30 days remaining
if [ "$expiry_days" -lt 30 ]; then
renew_certificate "$domain_name"
fi
fi
done
log_message "SSL certificate renewal check completed"
|
Security Best Practices
1. Certificate Security Management
- Private key protection: Ensure private key file permissions are 600, accessible only by root
- Regular rotation: Renew certificates every 90-180 days
- Backup strategy: Regularly back up certificate and private key files
- Access control: Restrict access to certificate files
2. Configuration Security
- Secure protocols: Enable only TLS 1.2 and TLS 1.3
- Strong cipher suites: Use strong encryption suites
- HTTP Strict Transport Security: Enable HSTS headers
- Security headers: Add appropriate security HTTP headers
3. Monitoring and Alerting
- Expiration monitoring: Monitor remaining certificate validity period
- Vulnerability scanning: Regularly scan SSL/TLS configuration vulnerabilities
- Performance monitoring: Monitor SSL/TLS handshake performance
- Access logging: Monitor abnormal access patterns
Troubleshooting
Common Issues and Solutions
1. Website Inaccessible After Certificate Installation
1
2
3
4
5
6
7
8
9
| # Check nginx configuration syntax
nginx -t
# Check certificate file permissions
ls -la /etc/ssl/certs/
ls -la /etc/ssl/private/
# Check if certificate is loaded correctly
openssl s_client -connect mail.example.com:443
|
2. Incomplete Certificate Chain
1
2
3
4
5
6
| # Check certificate chain
openssl s_client -connect mail.example.com:443 -showcerts
# Manually build certificate chain
cat intermediate.crt > fullchain.crt
cat your_domain.crt >> fullchain.crt
|
3. SSL/TLS Handshake Failure
1
2
3
4
5
| # Check protocol support
openssl s_client -connect mail.example.com:443 -tls1_2
# Check cipher suite support
openssl s_client -connect mail.example.com:443 -cipher ECDHE-RSA-AES128-GCM-SHA256
|
4. Incorrect Certificate Expiration Calculation
1
2
| # Calculate expiration precisely
openssl x509 -enddate -noout -in mail.example.com.crt | awk -F= '/notAfter/ {print "Expiration:", $2; print "Days remaining:", int((mktime(gensub(/(.+) (.+) (.+) (.+) (.+) (.+)/,"\\1 \\2 \\3 \\4 \\5 \\6","g", $2))-mktime(gensub(/(.+) (.+) (.+) (.+) (.+) (.+)/,"\\1 \\2 \\3 \\4 \\5 \\6","g","now")))/86400)}'
|
Log Analysis
1
2
3
4
5
6
7
8
| # View nginx error logs
tail -f /var/log/nginx/error.log
# View SSL errors
grep "SSL" /var/log/nginx/error.log
# Analyze SSL-related errors in access logs
grep "443" /var/log/nginx/access.log | grep -E "400|403|502"
|
Summary
Through this detailed guide, we’ve learned how to use Ansible for automated batch deployment and renewal management of SSL certificates. This solution offers the following advantages:
Key Benefits:
- High automation: Reduces manual operations and minimizes human error
- Consistency: Ensures uniform configuration across all servers
- Scalability: Easy to add new certificates and servers
- Comprehensive monitoring: Provides full certificate status monitoring
- Rapid response: Automated renewal and alerting mechanisms
Implementation Recommendations:
- Phased rollout: Verify in test environments first, then gradually promote to production
- Regular maintenance: Periodically review configurations and update best practices
- Documentation: Keep related documentation and operation manuals up to date
- Team training: Train relevant personnel to ensure correct usage
- Backup strategy: Establish comprehensive backup and recovery mechanisms
Future Directions:
- CI/CD integration: Integrate SSL certificate management into automated deployment workflows
- Cloud platform integration: Support cloud platform certificate management services
- Intelligent monitoring: Use AI technology for smarter monitoring and prediction
- Multi-platform support: Extend support to other web servers and application servers
- Toolchain integration: Integrate with other operations tools for more comprehensive management
SSL certificate automation management is an important part of modern operations. By properly using automation tools like Ansible, you can significantly improve operations efficiency, reduce security risks, and ensure stable operation of enterprise services. We hope the solution provided in this article offers valuable reference for your practical work.