How to Migrate an Existing Site to HTTPS with Let's Encrypt and Zero Downtime (2016)
Short answer: Install Certbot, get certificate with certbot --nginx -d example.com, add 301 redirect from HTTP, fix mixed content (HTTP resources on HTTPS page), update canonical URLs and sitemaps, notify Google Search Console of the protocol change. In 2016, Certbot was still young — use the --nginx plugin, but verify its edits before reloading.
Install Certbot (2016 method)
# Ubuntu 14.04/16.04 — Certbot added to apt in 2016
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install python-certbot-nginx
# Alternatively (original method from 2015):
sudo wget https://dl.eff.org/certbot-auto
chmod +x certbot-auto
Get Certificate
# Nginx plugin — automatically edits nginx config
sudo certbot --nginx -d example.com -d www.example.com
# Webroot method (safer — no nginx editing):
sudo certbot certonly \
--webroot \
--webroot-path=/var/www/html \
-d example.com \
-d www.example.com \
--email admin@example.com \
--agree-tos \
--no-eff-email
# Certificates stored in:
# /etc/letsencrypt/live/example.com/fullchain.pem
# /etc/letsencrypt/live/example.com/privkey.pem
Nginx Configuration After Migration
# /etc/nginx/sites-available/example.com
# Step 1: Keep HTTP temporarily for ACME challenges
server {
listen 80;
server_name example.com www.example.com;
# Let's Encrypt renewal
location /.well-known/acme-challenge/ {
root /var/www/html;
try_files $uri =404;
}
# Everything else → HTTPS
location / {
return 301 https://example.com$request_uri;
}
}
# Step 2: Redirect www → non-www
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://example.com$request_uri;
}
# Step 3: Main HTTPS server
server {
listen 443 ssl http2; # http2 available in nginx 1.9.5+, 2016 standard
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Modern SSL config (2016 Mozilla Intermediate)
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:kEDH+AESGCM:!aNULL:!eNULL:!EXPORT;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
root /var/www/html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS on;
}
}
Fix Mixed Content
Mixed content (HTTP resources loaded on HTTPS page) causes browser warnings and blocks:
# Find mixed content in codebase
grep -r "http://" /var/www/html/templates/
grep -r 'src="http://' /var/www/html/templates/
grep -r "url('http://" /var/www/html/assets/css/
<?php
// PHP: replace hardcoded http:// URLs in database content
// This handles CMS content stored with http:// URLs
function forceHttpsInContent(string $content): string {
return str_replace('http://example.com', 'https://example.com', $content);
}
// Or: use protocol-relative URLs (works for external resources)
// http://cdn.example.com/file.js → //cdn.example.com/file.js
<!-- HTML: protocol-relative for external scripts/styles -->
<!-- OLD: -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<!-- NEW: -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<!-- Or simply HTTPS: -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<?php
// WordPress: update all URLs in database after HTTPS migration
// Via WP-CLI:
// wp search-replace 'http://example.com' 'https://example.com' --all-tables
// Manual SQL:
UPDATE wp_options SET option_value = REPLACE(option_value, 'http://example.com', 'https://example.com');
UPDATE wp_posts SET post_content = REPLACE(post_content, 'http://example.com', 'https://example.com');
UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, 'http://example.com', 'https://example.com');
Update Canonical URLs and Sitemap
<?php
// After migration: update canonical URLs in code
// Before:
$canonicalUrl = 'http://example.com' . $page['path'];
// After:
$canonicalUrl = 'https://example.com' . $page['path'];
// Or dynamically:
$protocol = isset($_SERVER['HTTPS']) ? 'https' : 'http';
$canonicalUrl = $protocol . '://example.com' . $page['path'];
<!-- sitemap.xml: update all URLs to https:// -->
<!-- Before: -->
<loc>http://example.com/about/</loc>
<!-- After: -->
<loc>https://example.com/about/</loc>
Notify Google Search Console
1. Add HTTPS property to Google Search Console:
- Add: https://example.com (new property)
- Google treats http:// and https:// as different sites
2. Update preferred domain setting in old HTTP property:
- Search Console → Settings → Preferred domain
- With 301 redirects, Google will consolidate PageRank
3. Submit new sitemap:
- Search Console → Crawl → Sitemaps → Submit
- URL: https://example.com/sitemap.xml
4. Monitor for crawl errors in both properties for 4 weeks
Auto-Renewal Setup
# Certbot auto-renewal (test first):
sudo certbot renew --dry-run
# Cron job (runs twice daily):
crontab -e
# Add:
0 2,14 * * * /usr/bin/certbot renew --quiet && systemctl reload nginx
# Systemd timer (Ubuntu 16.04 default):
# Certbot package installs /etc/cron.d/certbot automatically
# Verify:
cat /etc/cron.d/certbot
# 0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew
Post-Migration Checklist
☑ Certificate installed, nginx serves HTTPS
☑ HTTP → HTTPS 301 redirects working
☑ www → non-www (or vice versa) 301 redirect
☑ No mixed content in browser console
☑ Canonical URLs updated to https://
☑ Sitemap updated to https://
☑ Google Search Console: new https property added
☑ New sitemap submitted
☑ Auto-renewal cron configured
☑ HSTS header present: Strict-Transport-Security
☑ Test on SSL Labs: A or A+ rating
☑ Monitor Search Console for 4 weeks (expect temporary ranking fluctuation)
Google confirmed in 2016 that HTTPS is a ranking signal. Sites that migrated saw small but measurable ranking improvements, especially in competitive niches. The SEO benefit wasn't the main driver — user trust and browser warnings were. But the ranking uplift was a welcome bonus.