Skip to content
rusthacks.dev
Security

Security Headers Set From a Rust Web Service Step by Step

Set the HTTP response headers that harden a web service: Content-Security-Policy, X-Content-Type-Options, Strict-Transport-Security, Referrer-Policy and more, applied with tower-http in Axum.

2 min read
Summarize in AI
Flat illustration for the post “Security Headers Set From a Rust Web Service Step by Step”, composed around web.

Security headers are the cheapest hardening available, and they are one line each. The difficulty is knowing which ones apply and not breaking the page with a policy that is too strict. This post lists the headers that earn their place, shows them added in Axum with tower-http, and notes the ones to skip. It belongs to the deployment controls in security for Rust developers.

Axum 0.8, tower-http 0.6, Rust 1.90.

A layer that sets them

Cargo.tomlTOML
tower-http = { version = "0.6", features = ["set-header"] }
rust
use axum::{http::{header, HeaderValue}, routing::get, Router};
use tower_http::set_header::SetResponseHeaderLayer;

fn app() -> Router {
    Router::new()
        .route("/", get(|| async { "ok" }))
        .layer(SetResponseHeaderLayer::overriding(
            header::X_CONTENT_TYPE_OPTIONS,
            HeaderValue::from_static("nosniff"),
        ))
        .layer(SetResponseHeaderLayer::overriding(
            header::REFERRER_POLICY,
            HeaderValue::from_static("strict-origin-when-cross-origin"),
        ))
        .layer(SetResponseHeaderLayer::overriding(
            header::STRICT_TRANSPORT_SECURITY,
            HeaderValue::from_static("max-age=31536000; includeSubDomains"),
        ))
}

Every response now carries the three headers. overriding replaces any value set by a handler, and if_not_present leaves handler values alone, which suits headers a handler may tune. Check with curl -I localhost:3000.

The headers

HeaderValuePurpose
X-Content-Type-Optionsnosniffbrowser trusts your Content-Type, preventing script execution of uploads
Strict-Transport-Securitymax-age=31536000; includeSubDomainsHTTPS only for a year; send only over HTTPS
Referrer-Policystrict-origin-when-cross-originlimits URL leakage to other sites
Content-Security-Policysee belowrestricts script, style and frame sources
X-Frame-Options or CSP frame-ancestorsDENY / frame-ancestors 'none'blocks clickjacking by framing
Permissions-Policycamera=(), microphone=()turns off browser features you do not use
Cross-Origin-Opener-Policysame-originisolates the browsing context

Skip X-XSS-Protection: modern browsers removed the filter, and the header can introduce issues in old ones; set it to 0 or leave it out.

Content-Security-Policy

For an API that never serves HTML:

Text
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'

For a site serving pages, start in report-only mode and tighten:

Text
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'

Watch the browser console or a report endpoint, fix what would break, then switch to the enforcing header. Inline scripts need a per-response nonce, not 'unsafe-inline'. A policy that is permissive everywhere protects nothing.

HSTS carefully

HSTS makes browsers refuse plain HTTP for the whole max-age. Roll out with a short value (max-age=300), confirm every subdomain supports HTTPS, then extend. Adding preload submits you to a list that is hard to leave. Do not send it over plain HTTP; browsers ignore it there.

Reverse proxies

If a proxy terminates TLS, HSTS should be added where you know the connection is HTTPS, typically at the proxy. Do not duplicate headers in two places; the browser may see two conflicting values.

Limits

Headers are defense in depth. They do not fix XSS in your templates, CSRF in your forms or broken authorization. Cookies need their own flags: Secure, HttpOnly, SameSite. Combined with CORS they define what a browser lets pages do. MDN's HTTP security headers documentation is the readable primary reference.

Also inWeb
Taggedheaders
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.