HSTS on Deno Deploy is one of those security controls that’s easy to enable and surprisingly easy to get wrong.
If you’re serving anything real on Deno Deploy, you should at least make an intentional decision about HTTP Strict Transport Security instead of leaving it as “probably fine.” HSTS tells browsers: only talk to this site over HTTPS for a set period of time. That shuts down a whole class of downgrade and SSL-stripping attacks.
The catch: HSTS is sticky. Once a browser sees it, you don’t get a quick undo button.
What HSTS does on Deno Deploy
HSTS is just an HTTP response header:
Strict-Transport-Security: max-age=31536000; includeSubDomains
When a browser receives that header over HTTPS, it remembers the rule for max-age seconds. On future visits, it won’t even try plain HTTP first.
That matters because the first insecure request is often the weak spot. Without HSTS, an attacker on a hostile network can interfere before your redirect to HTTPS happens. With HSTS already cached, the browser skips that insecure hop.
On Deno Deploy, HTTPS termination is handled by the platform, so you’re not configuring TLS at the edge yourself. Your job is simpler: return the right header consistently from your app.
The short version
If I were choosing a default policy for most Deno Deploy apps:
- Start with HSTS enabled
- Use a modest
max-agefirst - Avoid
includeSubDomainsunless you actually control all subdomains - Avoid preload until you really mean it
That’s the safe, boring path. Boring is good in security.
Pros of HSTS on Deno Deploy
1. It closes the “first redirect” gap
A plain HTTP to HTTPS redirect is not enough by itself. The browser still has to make that first insecure request. HSTS removes that after the first successful secure visit.
For login pages, dashboards, admin tools, and APIs called from browsers, this is a real win.
2. Deno Deploy makes rollout easy
You don’t need to touch nginx, Apache, or a load balancer. Just set the header in your response path.
A minimal Deno server looks like this:
Deno.serve((req) => {
const body = "Hello from Deno Deploy";
return new Response(body, {
headers: {
"content-type": "text/plain; charset=utf-8",
"strict-transport-security": "max-age=86400",
},
});
});
That’s it. Operationally, Deno Deploy is a nice place to use HSTS because the HTTPS side is already managed for you.
3. It’s cheap security
HSTS has almost no runtime cost. No extra round trips. No expensive logic. No dependency. It’s one header with a lot of leverage.
That’s rare.
4. It pairs well with a broader header policy
HSTS shouldn’t be your only header, but it fits naturally with CSP, X-Content-Type-Options, Referrer-Policy, and friends.
If you want a quick sanity check on what your app is returning, run a free scan at HeaderTest. It’s a fast way to catch missing or inconsistent headers before you ship.
Cons of HSTS on Deno Deploy
1. Bad HSTS config is painful to reverse
This is the big one.
If you set a long max-age and later realize you broke access for some environment or subdomain, browsers will keep enforcing HTTPS until the cached policy expires.
That means HSTS is not like tweaking a normal redirect rule. Mistakes linger.
2. includeSubDomains can blow up faster than people expect
This directive sounds reasonable:
Strict-Transport-Security: max-age=31536000; includeSubDomains
But it applies to every subdomain. That includes weird old hosts, forgotten staging systems, vendor-managed DNS names, and internal tools that accidentally sit under the same parent domain.
If even one subdomain can’t serve valid HTTPS reliably, users may get hard failures.
I’ve seen teams turn this on because a checklist said so, then spend the next week figuring out why old-admin.example.com is suddenly dead.
3. Preload is basically a commitment, not a tweak
You’ll often see this recommended:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Preload means asking browser vendors to bake your domain into their HSTS preload lists. That protects even first-time visitors, which is great.
It also means you’re making a long-term promise:
- HTTPS must work everywhere
- all subdomains must support it
- your redirects and cert management need to stay clean
If your domain strategy is messy, preload is not your friend.
4. Local dev and preview environments can get weird
This isn’t unique to Deno Deploy, but it shows up often. If you test with real domains and HSTS gets cached in your browser, switching environments can become annoying.
You think your app is misbehaving, but your browser is just force-upgrading requests because of an earlier HSTS policy.
That’s why I prefer to be conservative in non-production setups.
Deno Deploy-specific comparison: where to set HSTS
There are basically three patterns.
Option 1: Set HSTS directly in every response
function withSecurityHeaders(headers = new Headers()) {
headers.set("strict-transport-security", "max-age=86400");
return headers;
}
Deno.serve(() => {
return new Response("ok", {
headers: withSecurityHeaders(),
});
});
Pros
- Explicit
- Easy to understand
- No framework magic
Cons
- Easy to forget on some routes
- Can get repetitive
- Risk of inconsistent headers across handlers
This is fine for tiny services.
Option 2: Use middleware or a shared wrapper
If you’re using Fresh, Hono, or your own routing layer, centralizing header logic is better.
Example with Hono:
import { Hono } from "jsr:@hono/hono";
const app = new Hono();
app.use("*", async (c, next) => {
await next();
c.res.headers.set("Strict-Transport-Security", "max-age=86400");
});
app.get("/", (c) => c.text("Hello"));
Deno.serve(app.fetch);
Pros
- Consistent
- Easier to audit
- Better for real apps
Cons
- Slightly less obvious than per-route code
- Middleware order can matter
This is my preferred setup for anything bigger than a toy app.
Option 3: Different policies by environment
const isProd = Deno.env.get("DENO_DEPLOYMENT_ID") !== undefined;
const hsts = isProd
? "max-age=31536000"
: "max-age=0";
Deno.serve(() =>
new Response("env aware", {
headers: {
"Strict-Transport-Security": hsts,
},
})
);
Pros
- Safer rollout
- Prevents dev/staging headaches
- Lets you ramp up gradually
Cons
- More conditional logic
- You need to be disciplined about environment detection
For teams with multiple environments, this is usually the least painful approach.
Recommended rollout strategy
If you’re adding HSTS to a Deno Deploy app today, I’d do it in phases.
Phase 1: Start small
Use:
Strict-Transport-Security: max-age=86400
That’s one day. Long enough to validate behavior, short enough that mistakes don’t haunt you for months.
Phase 2: Increase confidence
Once you know HTTPS is stable and every production route behaves correctly:
Strict-Transport-Security: max-age=31536000
One year is a common final value.
Phase 3: Decide carefully on subdomains
Only add this if you truly control your whole namespace:
Strict-Transport-Security: max-age=31536000; includeSubDomains
If you’re not 100% sure, don’t do it yet.
Phase 4: Treat preload like a product decision
Only consider this when:
- your main domain is permanently HTTPS-only
- every subdomain is HTTPS-capable
- you understand the removal process is slow and annoying
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Plenty of solid production apps never need preload.
When HSTS is a strong fit for Deno Deploy
HSTS is a great fit when:
- your site is public and always HTTPS
- you have login or authenticated sessions
- you serve a stable domain
- you control your subdomains well
- you want simple, low-maintenance security wins
That describes a lot of Deno Deploy apps.
When to be cautious
Slow down if:
- you have legacy subdomains
- you use ad hoc staging hosts under the same parent domain
- your DNS is messy
- different teams own different subdomains
- you’re tempted to preload because “security best practice”
A lot of HSTS pain comes from organizational mess, not technical difficulty.
My practical recommendation
For most Deno Deploy projects, enable HSTS in app-level middleware and start with:
Strict-Transport-Security: max-age=86400
After a clean rollout, move to:
Strict-Transport-Security: max-age=31536000
Only add includeSubDomains if you’ve audited every subdomain. Only add preload if you’d bet your weekend on never needing to back it out.
That’s the real comparison here. HSTS itself is excellent. Aggressive HSTS is where teams get into trouble.
On Deno Deploy, implementation is easy. The hard part is being honest about your domain hygiene.