2 · Subdomains + SSL
Objective — give staging and production each a reachable hostname and a valid certificate before the first deploy (Deployer flips a symlink, it doesn’t issue certs), so the deploy lands on a working HTTPS origin.
Background
Section titled “Background”Staging and production each need a reachable hostname and a valid certificate before the first deploy — Deployer flips a symlink, it doesn’t issue certs. This page gets DNS and TLS green so the deploy lands on a working HTTPS origin.
1. Plan the subdomain map
Section titled “1. Plan the subdomain map”Decide the hostnames up front so DNS, certs, and your app’s APP_URL all agree.
| Environment | Example host | Points to |
|---|---|---|
| Staging | staging.example.com | Staging server IP (or Cloudflare proxy) |
| Production | app.example.com / example.com | Production server IP |
| Apex redirect | example.com → www (or vice-versa) | Pick one canonical, 301 the other |
Keep apex vs www decisions consistent with APP_URL in 3 · Production .env. A mismatch breaks absolute URLs, signed routes, and cookie domains.
2. Add the DNS records
Section titled “2. Add the DNS records”Create one record per host at your DNS provider (or Cloudflare — see 6 · Cloudflare CDN).
| Type | Name | Value | Notes |
|---|---|---|---|
A | staging | <server IPv4> | Apex/subdomain → IP |
AAAA | staging | <server IPv6> | Only if the server has IPv6 |
CNAME | www | example.com | Alias one name to the canonical |
3. Provision the SSL/TLS certificate
Section titled “3. Provision the SSL/TLS certificate”Use your host’s automated Let’s Encrypt integration (cPanel AutoSSL, Forge, Ploi, or certbot). One cert per hostname, or a wildcard if your panel supports DNS-01.
- Issue for every hostname that serves traffic, including
www. - Enable auto-renewal — Let’s Encrypt certs expire every 90 days.
- Force HTTP → HTTPS at the web server (or via Cloudflare “Always Use HTTPS”).
4. Verify with five CLI checks
Section titled “4. Verify with five CLI checks”Don’t trust the browser — run these and read the output.
-
Run the five-check verification pass.
Terminal window # 1) DNS resolves to the expected IPdig +short staging.example.com# 2) HTTPS responds (expect HTTP/2 200 or 301/302 to the canonical host)curl -sI https://staging.example.com | head -1# 3) HTTP redirects to HTTPS (expect 301/308 with a https:// Location)curl -sI http://staging.example.com | grep -iE 'HTTP/|location'# 4) Behind Cloudflare? Confirm the proxy is in frontcurl -sI https://staging.example.com | grep -i 'cf-ray' && echo "proxied" || echo "direct"# 5) Certificate is valid and covers the hostecho | openssl s_client -connect staging.example.com:443 -servername staging.example.com 2>/dev/null \| openssl x509 -noout -subject -issuer -dates# Expected: dig returns your IP, HEAD is 200/301, HTTP redirects to HTTPS, and the cert notAfter is in the future with subject matching the host- ✅
digreturns your IP, the HEAD request is200/301, HTTP redirects to HTTPS, and the cert’snotAfterdate is in the future andsubjectmatches the host.
- ✅
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
dig returns nothing / wrong IP | Record missing or not propagated | Re-check the record; wait for TTL |
curl cert error | Cert not issued / wrong host | Re-run AutoSSL; ensure host is in the cert SAN |
| Redirect loop | Cloudflare SSL mode Flexible + origin forcing HTTPS | Set Cloudflare SSL to Full (Strict) (see page 6) |
NET::ERR_CERT_COMMON_NAME_INVALID | Cert doesn’t list this host | Reissue covering all hostnames |
Checklist
Section titled “Checklist”Do not mark this step done until every box below is checked.
- 🤖 Hostnames resolve — every environment hostname resolves via
digto the correct IP. - 🤖 HTTPS + redirect — HTTPS returns 200/301 and HTTP force-redirects to HTTPS.
- 👤 Cert valid + auto-renew — certificate is valid, covers the host, and auto-renewal is on.
- 🔀 Canonical matches
APP_URL— apex vswwwdecision matches the plannedAPP_URL.