Security

Security Headers & TLS Hardening for Production APIs: HSTS, CSP, CORS & TLS 1.3 in 2026

Security headers and TLS configuration are the most commonly skipped production security controls — they don't affect functionality, so they're easy to ignore. But OWASP A05:2021 Security Misconfiguration includes missing security headers, and weak TLS enables MITM attacks that steal session tokens and API credentials. This guide gives you a complete, copy-paste Spring Boot configuration for headers and TLS that passes SSL Labs A+ and securityheaders.com A grade.

Md Sanwar Hossain April 6, 2026 17 min read TLS & Headers
Security headers and TLS hardening for production APIs HSTS CSP CORS TLS 1.3 Spring Boot

TL;DR — Test Before You Ship

"Misconfigured security headers and weak TLS are silent vulnerabilities — your API works fine, but attackers can exploit clickjacking, MIME sniffing, cookie theft, or downgrade attacks. Implement HSTS with preload, strict CSP, CORS allowlist, X-Frame-Options, and TLS 1.3-only. Test with securityheaders.com and SSL Labs before every production release."

Table of Contents

  1. Why Security Headers & TLS Are Skipped and Shouldn't Be
  2. HTTP Security Headers: The Complete List
  3. HSTS: HTTP Strict Transport Security
  4. Content Security Policy (CSP) for APIs
  5. CORS: The Most Misunderstood Security Control
  6. Spring Boot Security Headers Configuration
  7. TLS 1.3: Configuration & Cipher Suite Hardening
  8. Certificate Lifecycle: Let's Encrypt, ACM & OCSP
  9. Testing: SSL Labs, securityheaders.com & HSTS Preload
  10. Security Headers Checklist for Production
  11. Conclusion

1. Why Security Headers & TLS Are Skipped and Shouldn't Be

OWASP A05:2021 — Security Misconfiguration is the fifth most critical web application security risk, affecting 90% of applications tested. Missing security headers are explicitly called out as a misconfiguration. They're skipped because: they have no functional impact in normal usage, they're not tested by default in integration or unit tests, and developers rarely see the consequences until an actual attack.

The attacks they prevent are real: clickjacking (iframe embedding to trick users into unintended clicks), MIME-type sniffing attacks, protocol downgrade attacks from HTTPS to HTTP (enabling MITM), cookie theft via insecure cookie attributes, and cross-site scripting enabled by missing CSP. Spring Boot's default security headers are better than none but insufficient for production.

2. HTTP Security Headers: The Complete List

Header Attack Prevented Recommended Value
Strict-Transport-Security HTTP downgrade / MITM max-age=31536000; includeSubDomains; preload
Content-Security-Policy XSS, data injection default-src 'none'; frame-ancestors 'none'
X-Frame-Options Clickjacking DENY
X-Content-Type-Options MIME sniffing attacks nosniff
Referrer-Policy Referrer information leak strict-origin-when-cross-origin
Permissions-Policy Camera/mic/geolocation abuse camera=(), microphone=(), geolocation=()
X-XSS-Protection Legacy XSS filter 0 (deprecated — use CSP instead)

3. HSTS: HTTP Strict Transport Security

HSTS instructs browsers to only communicate with your domain over HTTPS — no HTTP fallback, ever. The preload directive goes further: browsers ship with a preloaded HSTS list, so they never send even the first request over HTTP, even to a domain they've never visited before.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

// Spring Security Java config
http.headers(headers -> headers
    .httpStrictTransportSecurity(hsts -> hsts
        .maxAgeInSeconds(31536000)
        .includeSubDomains(true)
        .preload(true)
    )
);

HSTS Preload requirements: serve only HTTPS (no HTTP), have HSTS header with max-age ≥ 31536000, includeSubDomains, and preload directive. Submit at hstspreload.org. Warning: once on the preload list, removing yourself takes months.

4. Content Security Policy (CSP) for APIs

CSP for REST APIs is simpler than for web apps: since browsers don't render your API responses as HTML, you only need to prevent your API from being embedded in frames and restrict what can be loaded if content is ever rendered.

# For REST APIs (no HTML rendering):
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'

# For web apps with inline scripts (use nonces):
Content-Security-Policy: default-src 'self';
  script-src 'self' 'nonce-{RANDOM_NONCE}';
  style-src 'self' 'nonce-{RANDOM_NONCE}';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.myservice.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  report-uri https://mdsanwarhossain.me/csp-report

# Spring Security: add via headers filter
http.headers(h -> h.contentSecurityPolicy(csp ->
    csp.policyDirectives("default-src 'none'; frame-ancestors 'none'")
));

5. CORS: The Most Misunderstood Security Control

Critical Misconception: CORS is enforced by browsers, not by servers. A server with Access-Control-Allow-Origin: * will still respond to requests from curl, Postman, or malicious server-side code. CORS only protects against browser-based cross-origin requests — it is NOT a substitute for authentication and authorization.

// WRONG — allows any origin with credentials
@CrossOrigin(origins = "*", allowCredentials = "true")
// This causes a browser error but signals misconfiguration

// CORRECT — explicit origin allowlist
@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(List.of(
        "https://app.myservice.com",
        "https://admin.myservice.com"
    ));
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-Request-ID"));
    config.setAllowCredentials(true);
    config.setMaxAge(3600L);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/api/**", config);
    return source;
}
Security headers and TLS hardening stack: HSTS, CORS, CSP, X-Frame-Options, certificate transparency
Security Headers & TLS Hardening Stack — layered from transport to content controls. Source: mdsanwarhossain.me

6. Spring Boot Security Headers Configuration

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .csrf(AbstractHttpConfigurer::disable) // REST APIs use stateless JWT
            .headers(headers -> headers
                .httpStrictTransportSecurity(hsts -> hsts
                    .maxAgeInSeconds(31536000)
                    .includeSubDomains(true)
                    .preload(true))
                .frameOptions(frame -> frame.deny())
                .contentTypeOptions(Customizer.withDefaults())
                .referrerPolicy(referrer -> referrer
                    .policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
                .permissionsPolicy(permissions -> permissions
                    .policy("camera=(), microphone=(), geolocation=(), interest-cohort=()"))
                .contentSecurityPolicy(csp -> csp
                    .policyDirectives("default-src 'none'; frame-ancestors 'none'"))
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        return http.build();
    }
}

7. TLS 1.3: Configuration & Cipher Suite Hardening

TLS 1.0 and 1.1 are officially deprecated (RFC 8996). PCI-DSS 3.2+ requires disabling TLS 1.0. TLS 1.2 is acceptable but TLS 1.3 is preferred: it eliminates weak cipher suites, mandates Perfect Forward Secrecy (PFS), and reduces handshake latency by one round-trip. Java 11+ (JDK 11) supports TLS 1.3 natively.

# application.yml — Spring Boot embedded Tomcat TLS config
server:
  port: 8443
  ssl:
    enabled: true
    enabled-protocols: TLSv1.3,TLSv1.2
    protocol: TLS
    key-store: classpath:keystore.p12
    key-store-password: ${SSL_KEYSTORE_PASSWORD}
    key-store-type: PKCS12
    # TLS 1.3 cipher suites (Java 11+)
    ciphers: >
      TLS_AES_256_GCM_SHA384,
      TLS_AES_128_GCM_SHA256,
      TLS_CHACHA20_POLY1305_SHA256,
      TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
      TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

Key cipher suite properties: All TLS 1.3 cipher suites provide AEAD (authenticated encryption) and mandatory PFS via ECDHE key exchange. Avoid: RSA key exchange (no PFS), RC4, 3DES, MD5, SHA-1 based suites, anonymous DH suites.

8. Certificate Lifecycle: Let's Encrypt, ACM & OCSP

Certificate expiry is one of the most common self-inflicted production outages. Automate certificate lifecycle end-to-end.

TLS certificate lifecycle and auto-renewal flow: certificate request to ACME Let's Encrypt to deployment to auto-renewal
TLS Certificate Lifecycle & Auto-Renewal Flow — from request to production deployment. Source: mdsanwarhossain.me

9. Testing: SSL Labs, securityheaders.com & HSTS Preload

10. Security Headers Checklist for Production

  • HSTS with max-age=31536000, includeSubDomains, preload — submitted to HSTS preload list
  • No allowedOrigins("*") with allowCredentials — use explicit origin allowlist in CORS config
  • Content-Security-Policy header set — default-src 'none' for REST APIs, nonce-based for web apps
  • X-Frame-Options: DENY — prevents clickjacking (use frame-ancestors 'none' in CSP as well)
  • X-Content-Type-Options: nosniff — prevents MIME type sniffing attacks
  • Referrer-Policy: strict-origin-when-cross-origin — prevents referrer information leakage
  • TLS 1.2 minimum, TLS 1.3 preferred — no SSLv3, no TLS 1.0, no TLS 1.1
  • Only AEAD cipher suites — no RC4, 3DES, MD5, SHA-1, no anonymous DH
  • Certificate expiry alert at 30 and 7 days — auto-renewal configured (certbot/cert-manager/ACM)
  • SSL Labs A+ grade verified — run before each production release
  • securityheaders.com A grade verified — all required headers present and correctly configured
  • CT log monitoring enabled — crt.sh alerts for unexpected certificate issuance on your domains

11. Conclusion

Security headers and TLS hardening are high-value, low-effort production security controls. The Spring Security configuration in Section 6, combined with the TLS configuration in Section 7, takes under an hour to implement and gives you protection against clickjacking, MIME sniffing, protocol downgrade attacks, and weak cipher exploitation.

Run SSL Labs and securityheaders.com after every significant infrastructure change. These free tools give you an objective, external view of your security posture that internal testing often misses. A+ on SSL Labs and A on securityheaders.com should be a build gate for every production release, not an afterthought.

security headers production HSTS preload Content Security Policy CORS misconfiguration TLS 1.3 Spring Boot cipher suite hardening X-Frame-Options Spring Security headers certificate lifecycle SSL Labs A+

Leave a Comment

Related Posts

Md Sanwar Hossain - Software Engineer
Md Sanwar Hossain

Software Engineer · Java · Spring Boot · Microservices · AI/LLM Systems

All Posts
Last updated: April 6, 2026