Skip to content
prod 352bb92
Browse

2 · Environment domains + SSL

Objective — give each environment row in Zaj-PROJECT.md 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.

Steps at a glance:

  1. Plan the environment hostname map — Decide the hostnames up front so DNS, certs, and your app’s APP_URL all agree.
  2. Add the DNS records — Create one record per host at your DNS provider (or Cloudflare — see 6 · Cloudflare CDN).
  3. Provision the SSL/TLS certificate — Use your host’s automated Let’s Encrypt integration (cPanel AutoSSL, Forge, Ploi, or certbot).
  4. Verify with five CLI checks — Don’t trust the browser — run these and read the output.

Every non-production and production row in Zaj-PROJECT.md needs 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. The common one-staging setup is only an example; projects can use qa, uat, client-demo, sandbox, or several non-production rows.

Decide the hostnames up front so DNS, certs, and your app’s APP_URL all agree.

Environment keyExample hostPoints to
staging-primarynonprod.example.comSelected non-production server IP (or Cloudflare proxy)
qa / uat / client-demoqa.example.com / uat.example.com / demo.example.comThe server IP recorded for that environment row
Productionapp.example.com / example.comProduction server IP
Apex redirectexample.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.

Record the final URL/domain, host/IP, and noindex/robots posture in Zaj-PROJECT.md before you create records. If there is no non-production URL yet, leave that row pending and do not enter Phase 5 until a real target is selected.

Create one record per host at your DNS provider (or Cloudflare — see 6 · Cloudflare CDN).

TypeNameValueNotes
A<non-production-host><server IPv4>Use the host from the selected Zaj-PROJECT.md row
AAAA<non-production-host><server IPv6>Only if the server has IPv6
CNAMEwwwexample.comAlias one name to the canonical

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”).

Don’t trust the browser — run these and read the output.

  1. Run the five-check verification pass.

    Terminal window
    HOST="nonprod.example.com" # replace with the selected URL/domain from Zaj-PROJECT.md
    # 1) DNS resolves to the expected IP
    dig +short "$HOST"
    # 2) HTTPS responds (expect HTTP/2 200 or 301/302 to the canonical host)
    curl -sI "https://$HOST" | head -1
    # 3) HTTP redirects to HTTPS (expect 301/308 with a https:// Location)
    curl -sI "http://$HOST" | grep -iE 'HTTP/|location'
    # 4) Behind Cloudflare? Confirm the proxy is in front
    curl -sI "https://$HOST" | grep -i 'cf-ray' && echo "proxied" || echo "direct"
    # 5) Certificate is valid and covers the host
    echo | openssl s_client -connect "$HOST:443" -servername "$HOST" 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
    • dig returns your IP, the HEAD request is 200/301, HTTP redirects to HTTPS, and the cert’s notAfter date is in the future and subject matches the host.
SymptomCauseFix
dig returns nothing / wrong IPRecord missing or not propagatedRe-check the record; wait for TTL
curl cert errorCert not issued / wrong hostRe-run AutoSSL; ensure host is in the cert SAN
Hostinger SSL stays InstallingLet’s Encrypt validation cannot reach the current origin, often because Cloudflare is still proxiedTemporarily DNS-only the record for issuance, cancel/retry SSL, then re-proxy after the cert is active
Cloudflare 525 on productionCloudflare reached an origin with no valid TLS, or DNS still points to a stale/wrong originFirst run the stale-DNS check against the current origin IP; then grey-cloud if needed, issue the origin cert, re-proxy, and use Full (Strict)
Redirect loopCloudflare SSL mode Flexible + origin forcing HTTPSSet Cloudflare SSL to Full (Strict) (see page 6)
NET::ERR_CERT_COMMON_NAME_INVALIDCert doesn’t list this hostReissue covering all hostnames

Do not mark this step done until every box below is checked.

  • 🤖 Hostnames resolve — every environment hostname resolves via dig to the correct IP.
  • 🤖 HTTPS + redirect — HTTPS returns 200/301 and HTTP force-redirects to HTTPS.
  • 🔀 Production Cloudflare safe — production is not orange-clouded until the stale-DNS check matches the current origin and origin TLS is valid; any live grey-cloud window had explicit approval and rollback notes.
  • 👤 Cert valid + auto-renew — certificate is valid, covers the host, and auto-renewal is on.
  • 🔀 Project state updatedZaj-PROJECT.md records each environment URL/domain, DNS target, and noindex/robots posture.
  • 🔀 Canonical matches APP_URL — apex vs www decision matches the planned APP_URL.