A new custom domain can answer plain HTTP
You point a custom domain at a self-hosted app, the platform provisions a certificate, and https://yourdomain.com loads with a padlock. That part usually works. The concrete launch risk is the request you did not test: http://yourdomain.com, without the s. On many self-hosted setups that plain-HTTP request is answered too, returning your app over an unencrypted connection instead of redirecting to HTTPS.
When that happens, the first request a visitor makes, before any redirect, travels in the clear. Anyone on the same network path can read it. If that request carries a session cookie, a login form post, or an API token in a header, it is exposed for that one hop. This maps to the OWASP class of sensitive data sent over an insufficiently protected channel, and to the CWE family around cleartext transmission. It is the same family of risk as cleartext transmission of credentials.
Separate what is observable from what is only possible here. Observable: whether http:// returns your app, returns a redirect, or refuses the connection. Also observable: whether your HTTPS responses carry a Strict-Transport-Security header. What is only possible, and not something you can prove from your own browser, is whether anyone has actually intercepted a cleartext request. The two header-and-redirect facts are checkable; the interception is not. Fix the checkable facts.
Why AI-built apps on self-hosted platforms hit this
A managed host like Vercel or Netlify tends to force HTTPS and emit HSTS for you. Self-hosted platforms behave differently. Coolify, Dokku, CapRover, and a plain reverse proxy in front of a container will provision a TLS certificate when you add a domain, but provisioning a certificate is not the same as redirecting HTTP or sending HSTS. Those are separate switches, and several of them are off by default.
The certificate makes https:// work, which makes the app look done. Nothing in the happy path forces you to open http:// and confirm it bounces. The plain-HTTP listener is often still bound on port 80 for the ACME challenge that issues the certificate, and it keeps serving the app unless you tell it to redirect.
- The padlock on https:// hides that http:// may still answer. You only see the gap if you type the insecure URL on purpose.
- Strict-Transport-Security is not added unless you set it. A fresh proxy config does not include it, so the browser is never told to upgrade future requests on its own.
- An AI builder adding a custom domain follows the certificate flow and stops there, because the app works. The redirect rule and the HSTS header are review steps that do not happen automatically, the same skipped-review pattern behind missing security headers in AI-built apps.
Check the redirect and the HSTS header on your own app
Both checks are safe to run against your own domain, and neither requires any attack. Run them after the domain is live but before you share it.
Check the HTTP-to-HTTPS redirect. From a terminal, request the plain-HTTP URL and look at the status line and Location header: curl -sI http://yourdomain.com. You can do the same in DevTools by typing http://yourdomain.com into the address bar and watching the Network tab for the first response.
Check for Strict-Transport-Security. Request the HTTPS URL and read the response headers: curl -sI https://yourdomain.com, or open DevTools, the Network tab, click the document request, and read Response Headers.
- Safer result: the first response is a redirect, status 301 or 308, with a Location header pointing at https://yourdomain.com. The browser upgrades before any app content loads.
- Suspicious result: the plain-HTTP request returns 200 and your actual page. That means the app is served over HTTP with no redirect, and the first hop was unencrypted.
- Also suspicious: a redirect that lands on HTTP somewhere, or a long chain that dips through an unencrypted host before settling on HTTPS.
- Safer result: a Strict-Transport-Security header is present with a non-trivial max-age, for example max-age=31536000. The browser will refuse plain HTTP to this host for that duration.
- Suspicious result: no Strict-Transport-Security header at all. HTTPS works, but the browser is never told to stay on it, so a future http:// link or typed address starts over the wire before any redirect.
- Check the apex and the www host separately, and any subdomain you serve. Each hostname has its own redirect behavior and its own headers.
Add the redirect and the header, and know what they miss
There are two distinct controls, and you want both. They live in your reverse proxy or platform config, not in app code.
Force HTTP to HTTPS. In Coolify, enable the force-HTTPS toggle on the application or domain so its Traefik proxy adds the redirect. On a hand-rolled Nginx or Caddy, add an explicit redirect from port 80 to the HTTPS host. After enabling it, re-run the plain-HTTP curl and confirm you now get a 301 or 308 to https://, because a toggle that is set is not the same as a redirect that fires.
Send Strict-Transport-Security on HTTPS responses. Add the header at the proxy, for example Strict-Transport-Security: max-age=31536000; includeSubDomains. Start with a shorter max-age while you confirm every subdomain genuinely serves HTTPS, then raise it. Only add includeSubDomains once you are sure no subdomain needs plain HTTP, and only add the preload directive when you actually intend to submit the domain to the browser preload list, because preload is hard to reverse.
- HSTS only protects browsers that have already received the header once over HTTPS. The very first visit to a brand-new domain is not covered unless the domain is on the preload list. The redirect is what protects that first hop in the meantime.
- The redirect and the header secure the transport. They do nothing about what the app does over that connection: an open API endpoint, a missing auth check, or an exposed build artifact is still exposed, just over HTTPS. Transport security is necessary, not sufficient.
- This is about encrypting the channel and forcing its use. It is not authentication and not authorization; a correctly redirected, HSTS-protected request to an endpoint with no access control still returns whatever that endpoint returns. Mixed-content issues, where an HTTPS page pulls sub-resources over HTTP, are a related but separate problem covered in the mixed content note.
Where VibeCodeGuard fits, and what it does not prove
VibeCodeGuard's Launch Check looks at exactly these two facts from the public URL. It requests your domain over plain HTTP and reports whether you get a redirect to HTTPS or an unencrypted 200, and it reads your HTTPS response headers and reports whether Strict-Transport-Security is present. Both are observable from outside without touching your code or server. A missing redirect or a missing HSTS header shows up with severity and fix direction alongside the rest of your header surface.
What the scan cannot do is judge your transport policy for you. It sees presence and the redirect outcome from the outside; it does not decide whether your max-age is right for your rollout, whether includeSubDomains is safe given your subdomains, or whether preload is a good idea for your domain. Those are decisions about your own infrastructure.
Next step: from a clean terminal, run the plain-HTTP curl against your apex and www hostnames and confirm each one returns a redirect to HTTPS, then confirm the HTTPS response carries Strict-Transport-Security. Run a focused Launch Check against the public URL before sharing it, so a missing redirect or header is something you catch and not your first visitor.
A present redirect and a present HSTS header prove the channel is encrypted and enforced. They do not prove the app behind it is safe. Authorization gaps, exposed endpoints, leaked secrets in the bundle, and business logic flaws are all untouched by TLS and need their own checks and manual review.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.