HTTPS for a Self-Hosted CMS: SSL Setup That Actually Works (2026)
Certbot on Nginx, auto-renewal, and the proxy gotchas
Chrome, Firefox, and Safari now slap a "Not Secure" warning on every page served over plain HTTP. As of early 2026, more than 95% of page loads in Chrome happen over HTTPS (per Google's Transparency Report). If your self-hosted CMS is still on port 80 with no certificate, you're that shrinking 5% — and every visitor sees the warning, every form is one browser update away from being blocked, and Google quietly ranks you below the encrypted competition.
The good news: adding SSL to a self-hosted CMS on a VPS is a 10-minute job with free tools. The tricky part isn't the certificate — it's the Laravel-behind-a-proxy gotchas that produce mixed-content errors and 419 CSRF failures after you flip the switch. This guide covers both.
TL;DR
HTTPS is mandatory now — browsers warn on HTTP, Google uses it as a ranking signal, and login forms leak passwords without it. For a self-hosted CMS on a VPS you have three real options: Let's Encrypt + Certbot (free, auto-renewing, the default choice), Cloudflare (free edge TLS, hides your origin IP), or a paid cert (only if you need an org-validated seal). Certbot on Nginx is the fastest path — one certbot --nginx command grabs the cert, edits your config, and sets up auto-renewal. After the cert works, the Laravel-specific fixes matter: set APP_URL=https://..., configure TRUSTED_PROXIES if you sit behind Cloudflare or a load balancer, and force HTTPS URLs so you don't get mixed-content or 419 errors. Then test at SSL Labs and aim for an A rating. UnfoldCMS is a plain Laravel app behind Nginx or Apache, so all of this applies to it exactly like any other Laravel deployment — the web server handles TLS, not the CMS. If you're still setting up the box, our self-hosted CMS setup guide covers the server prep first.
Why is HTTPS mandatory for a self-hosted CMS?
Three reasons, all non-negotiable. Browsers mark HTTP pages "Not Secure" and block features like geolocation and service workers on them. Google confirmed HTTPS as a ranking signal back in 2014 and has only leaned harder since. And any login or contact form on HTTP sends passwords and messages in plain text that anyone on the network can read.
For a CMS specifically, the admin panel is the killer. You log into /admin with a password. Over HTTP, that password crosses the wire unencrypted. Anyone sharing your coffee-shop Wi-Fi or sitting on a compromised router can grab it. That's not a theoretical risk — it's session hijacking 101. Security hardening starts with TLS, and our self-hosted CMS security guide treats it as step one for a reason.
What are my SSL options on a VPS?
Three paths. Let's Encrypt via Certbot issues free 90-day certs that auto-renew — best for most self-hosted setups. Cloudflare puts TLS at their edge and proxies your traffic, which also hides your origin IP and adds a CDN. A paid cert from DigiCert or Sectigo costs money and only matters if you need Organization Validation or a warranty.
For a single CMS on a $5 VPS, Let's Encrypt wins on cost and simplicity. If you already run a VPS on a tight budget, Certbot adds zero cost and no extra RAM overhead. Reach for Cloudflare when you also want caching and DDoS protection — see our CDN setup guide for that layer. Here's how the three compare:
| Option | Cost | Renewal | Hides origin IP | Best for |
|---|---|---|---|---|
| Let's Encrypt + Certbot | Free | Auto (90-day certs) | No | Most self-hosted CMS sites |
| Cloudflare (Full/Strict) | Free tier | Automatic at edge | Yes | Sites wanting CDN + DDoS shield |
| Paid cert (DigiCert etc.) | $50–$500/yr | Manual (1-yr) | No | Enterprises needing OV/EV + warranty |
How do I install SSL with Certbot on Nginx?
Install Certbot, point it at your Nginx site, and run one command. Certbot verifies you control the domain, downloads the cert, edits your Nginx config to serve HTTPS, and offers to redirect HTTP to HTTPS automatically. The whole thing takes under two minutes on a working DNS record.
Before you start, make sure your domain's A record points at the VPS IP and port 443 is open in your firewall. Then follow these steps:
-
Install Certbot and the Nginx plugin (Ubuntu/Debian):
sudo apt update sudo apt install certbot python3-certbot-nginx -y -
Confirm your Nginx server block has the right domain. Certbot reads
server_nameto know which cert to request:server { listen 80; server_name yourdomain.com www.yourdomain.com; root /var/www/yourcms/public; # ... your Laravel config } -
Run Certbot. It handles the cert request and Nginx edits:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com -
Choose the redirect option. When Certbot asks, pick "Redirect" so all HTTP traffic 301s to HTTPS.
-
Verify the cert is live:
sudo certbot certificates
That's it. Certbot has now rewritten your server block to listen on 443 with the cert paths filled in.
How does auto-renewal work?
Let's Encrypt certs expire every 90 days, so renewal has to be automatic or your site breaks four times a year. Certbot installs a systemd timer (or cron job) that checks twice daily and renews any cert within 30 days of expiry. You don't touch it after install — but you should test it once.
Confirm the timer is active and do a dry run:
sudo systemctl status certbot.timer
sudo certbot renew --dry-run
If the dry run reports success, real renewals will work the same way. Certbot reloads Nginx after each renewal so the new cert takes effect without you logging in. Set a calendar reminder to check SSL Labs once a year anyway — timers can silently fail if the box runs out of disk or Certbot gets a broken update.
How do I redirect HTTP to HTTPS and add HSTS?
Certbot's redirect option already handles the http→https 301. To go further, add an HSTS header that tells browsers to never even try HTTP for your domain. HSTS closes the tiny window where a first visit over HTTP could be intercepted before the redirect fires.
Add this inside your HTTPS server block in Nginx:
# Force HTTPS for one year, including subdomains
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Start with a short max-age (like 300) while you test, then bump it to 31536000 (one year) once you're sure HTTPS works everywhere. Only add includeSubDomains if every subdomain has a valid cert — otherwise you'll lock yourself out of an HTTP-only subdomain. Reload Nginx with sudo nginx -t && sudo systemctl reload nginx after any change.
Why does my CMS break behind Cloudflare or a proxy?
Because Laravel doesn't know it's behind HTTPS. When Cloudflare or a load balancer terminates TLS and forwards plain HTTP to your origin, Laravel sees http, generates http:// links and form actions, and the browser blocks them as mixed content. You also get 419 "Page Expired" errors on login because the CSRF cookie's secure flag doesn't line up.
The fix is telling Laravel to trust the proxy and know its real scheme. In your .env:
APP_URL=https://yourdomain.com
Then set trusted proxies. In modern Laravel this lives in bootstrap/app.php or via an env var:
# Trust all proxies (fine behind Cloudflare's known IPs)
TRUSTED_PROXIES=*
UnfoldCMS runs as a standard Laravel app, so it reads APP_URL and TRUSTED_PROXIES the same way. Setting both makes the CMS generate correct https:// URLs and honor the X-Forwarded-Proto header your proxy sends. This is the single most common self-host support issue, and it's a two-line config fix, not a code change. You can see the full feature set on our features page.
How do I fix mixed-content and 419 CSRF errors?
Mixed content happens when the page loads over HTTPS but pulls scripts, images, or CSS over HTTP. Force every generated URL to HTTPS and the warnings vanish. The 419 error is a separate CSRF-cookie problem fixed by making sure Laravel knows the request is secure.
For mixed content, force the scheme in a service provider (app/Providers/AppServiceProvider.php):
public function boot(): void
{
if ($this->app->environment('production')) {
\Illuminate\Support\Facades\URL::forceScheme('https');
}
}
For the 419 errors, TRUSTED_PROXIES usually fixes it — once Laravel sees the request as secure, it sets the CSRF cookie's Secure flag correctly and the token matches. If you set SESSION_SECURE_COOKIE=true, double-check your whole site is on HTTPS, because a secure cookie won't be sent over any leftover HTTP request. One more trap: don't set a domain-wide SESSION_DOMAIN that spans subdomains unless you mean to share cookies — a mismatched cookie domain also throws 419s.
Testing checklist
Don't trust the green padlock alone. Run the site through SSL Labs' server test (ssllabs.com/ssltest) and aim for an A or A+ rating — it catches weak ciphers, missing intermediate certs, and old TLS versions. Then walk through this list:
- Padlock in the browser on both the homepage and
/admin— no "Not Secure", no mixed-content warning in the console. - HTTP redirects to HTTPS — hit
http://yourdomain.comand confirm it 301s. - Login works with no 419 error — the CSRF check passes end to end.
- Certbot dry run passes —
sudo certbot renew --dry-runreturns success. - HSTS header present — check with
curl -I https://yourdomain.com | grep -i strict. - Cert covers www and root — SSL Labs flags a mismatch if
wwwisn't in the cert.
Fix anything red before you announce the site. A broken cert on /admin is worse than no cert because you'll assume you're safe.
FAQ
Does UnfoldCMS provision SSL certificates for me?
No. UnfoldCMS is a Laravel app that runs behind Nginx or Apache, and TLS is the web server's job. You install the cert with Certbot or put Cloudflare in front — the CMS then generates correct https:// URLs once APP_URL and TRUSTED_PROXIES are set.
Is Let's Encrypt good enough for production? Yes. Let's Encrypt certs use the same encryption as paid certs and are trusted by every major browser. The only thing you don't get is Organization Validation (the company name in the cert) or a warranty — features most self-hosted sites never need.
Why do I still get "Not Secure" after installing the cert?
Almost always mixed content — a script, image, or font loading over http://. Open the browser console, find the HTTP resource, and force HTTPS with URL::forceScheme('https') plus correct APP_URL. A single hardcoded http:// link in your content can trigger it too.
Do I need HSTS?
It's strongly recommended but optional. HSTS stops the brief HTTP window on a first visit from being hijacked. Test with a short max-age first, then raise it to a year once you're confident every URL and subdomain serves HTTPS.
Cloudflare Flexible vs Full — which SSL mode? Never use Flexible. It encrypts browser-to-Cloudflare but sends plain HTTP to your origin, which causes redirect loops and leaves the origin leg unencrypted. Use Full (Strict) with a valid origin cert (Let's Encrypt or a Cloudflare Origin cert) so both legs are encrypted.
Ship it
Get the cert with Certbot, redirect HTTP to HTTPS, add HSTS, set APP_URL and TRUSTED_PROXIES, and test at SSL Labs. That's the whole job — an afternoon at most, and free if you use Let's Encrypt. If you're running UnfoldCMS, none of this needs a plugin: it's a standard Laravel app, so the same web-server-level TLS setup works out of the box once you point the config at HTTPS. Browse the features page to see what else ships with a self-hosted install.
Sources: Google Transparency Report (HTTPS encryption on the web, 2026), Let's Encrypt documentation, Certbot official docs, Qualys SSL Labs testing guidance, and Laravel proxy/HTTPS documentation. Command examples tested on Ubuntu 22.04 / Nginx. Verify cert paths and firewall rules against your own distro.
Free & Open Source
Own your CMS. No subscriptions.
Unfold CMS is free to download and self-host. Built on Laravel + React, full source code included.
Share this post: