OneLinersCommand workbench
Guides
Security / Services & Applications

Harden Apache security headers and TLS policy

Deploy scoped response headers and modern TLS settings, validate application compatibility, prevent duplicate headers, and retain a fast rollback.

55 min8 stepsChanges system stateRevision 2
Save or explore
Save to collectionCreate a collection in the sidebar first.
0 of 8 steps completed
Goal

Reduce browser and transport attack surface without blindly applying headers that break application behavior.

Supported environments
  • Apache HTTP Server 2.4.x
  • OpenSSL 3.x
Prerequisites
  • Console and recovery access Keep an independent console session and a copy of the current Apache configuration before changing listeners, modules, or access rules.
  • DNS and network plan Resolve every production hostname to the intended public address and list the exact management and client networks that require access.getent ahosts {{domain}} && ip route show
  • Port ownership Confirm that TCP port 443 is free or already belongs to the intended Apache instance.sudo ss -lntp 'sport = :443'
  • Rollback artifact Snapshot the host or create an archive of the active configuration and web roots before the first material change.
Operating boundary

OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.

Full guide

What you will build

System
  • A site-scoped Apache hardening layer that applies conservative browser headers, an initially report-only Content Security Policy, a one-day HTTPS-only HSTS pilot, and a TLS 1.2/1.3 protocol floor.
  • An application compatibility and duplicate-header test plan covering redirects, login, APIs, uploads, assets, frames, multiple response statuses, and obsolete-protocol rejection.
  • A targeted rollback where individual included fragments can be removed without disturbing other virtual hosts, while acknowledging that browser-cached HSTS cannot be recalled instantly.
Observable outcome
  • Each intended response header appears exactly once on the selected HTTPS site, CSP remains report-only until violations are understood, and HSTS is emitted only on HTTPS.
  • TLS 1.2 and 1.3 succeed with the expected certificate while obsolete protocols fail, using distribution/OpenSSL defaults instead of a copied static cipher list.
  • Operators can explain ownership of every policy, identify application/CDN duplication, promote CSP/HSTS gradually, and roll back server configuration without breaking cached clients.

Architecture

How the parts fit together

Apache's selected TLS virtual host includes a domain-specific directory of small configuration fragments. `mod_headers` adds response policy at the edge, while the application continues to own content-specific CSP sources, cookies, and authorization. HSTS is conditional on HTTPS and starts with a short max-age without subdomains or preload. mod_ssl accepts only TLS 1.2/1.3 and uses maintained OpenSSL/distribution cipher defaults. Parser, external protocol probes, header cardinality checks, browser workflows, and reports form the acceptance gate.

Site-scoped include directoryContains independently removable headers, HSTS, and TLS fragments for one named virtual host.
mod_headersSets selected response headers consistently across normal and error responses.
Report-only CSPObserves policy violations before enforcement can block required application resources.
Pilot HSTS policyTells supporting browsers to use HTTPS for one day only after the domain is proven ready.
mod_ssl/OpenSSLEnforces the protocol floor and maintained cryptographic implementation.
Compatibility evidenceCombines command-line protocol/header checks with real browser and application-path testing.
  1. The client negotiates TLS 1.2 or 1.3 and Apache selects the intended SNI virtual host.
  2. The application or static handler produces a response, including its own content and cookies.
  3. Site-scoped mod_headers rules set conservative policy and report-only CSP, avoiding unrelated vhosts.
  4. Only an HTTPS request receives the pilot Strict-Transport-Security header.
  5. External probes count headers and protocols while browser/application tests reveal compatibility and CSP reports.

Assumptions

  • Every production path for `{{domain}}` already works over valid HTTPS, and HTTP redirects safely to the same HTTPS origin.
  • The selected site file can include `/etc/apache2/sites-available/{{domain}}.d/*.conf` without crossing tenant boundaries.
  • Application owners can test login, logout, reset links, API calls, uploads, downloads, frames, third-party scripts, fonts, images, and error pages.
  • The CDN/load balancer/application/header ownership map is known so duplicate or conflicting policy can be removed at the correct layer.
  • No subdomain or preload commitment is made in this pilot; those require a separate inventory and long-lived operational decision.

Key concepts

HSTS
A cached browser instruction to use HTTPS for a host for max-age seconds; server rollback cannot immediately erase a policy already received.
CSP report-only
A Content Security Policy that reports would-be violations without blocking them, supporting inventory before enforcement.
Header cardinality
The number of occurrences of a response header; duplicates can conflict or be interpreted differently.
nosniff
A response instruction that prevents selected MIME-type guessing and depends on correct Content-Type values.
TLS protocol floor
The oldest allowed TLS version, set here to 1.2 while also allowing 1.3.
Security header ownership
The single designated layer responsible for generating a policy so CDN, proxy, and application do not conflict.

Before you copy

Values used in this guide

{{domain}}

HTTPS ServerName whose policy is isolated and tested.

Example: portal.example.com
{{criticalPath}}

Representative authenticated or business-critical path used in compatibility checks.

Example: account

Security and production boundaries

  • HSTS is persistent client state. Start with a short max-age, omit includeSubDomains/preload, keep HTTPS continuously healthy, and lengthen only after monitoring.
  • A copied CSP can break applications or provide false confidence. Inventory sources, remove unsafe inline behavior where possible, collect reports safely, and enforce iteratively.
  • Headers do not fix authorization, injection, vulnerable dependencies, weak cookies, or application output encoding.
  • Do not set trust-sensitive policy globally on a multi-tenant server unless every vhost has the same tested ownership and behavior.
  • Static cipher lists age quickly. Prefer maintained platform defaults unless a current compliance profile requires an explicitly tested list.
  • CSP reports may include URLs or document context; apply privacy, retention, rate, and access controls to report collection.

Stop before continuing if

  • Stop if HTTPS, redirects, certificate names, existing headers, or application workflows are already unhealthy.
  • Do not enable HSTS when any necessary access path remains HTTP-only or TLS ownership/renewal is uncertain.
  • Do not enforce CSP while report-only violations remain unexplained or reporting telemetry is absent.
  • Do not reload after parser errors, duplicate includes, conflicting header owners, or an unreviewed global scope.
  • Roll back the specific fragment if critical paths, assets, APIs, uploads, frames, old approved clients, or error handling regress.
01

verification

Capture current headers and TLS

read-only

Save redirect, response-header, certificate, protocol, and application-console baselines before adding policy.

Why this step matters

Current redirect, header, certificate, protocol, and workflow behavior is the comparison point for every hardening change and reveals existing duplicates before new policy is added.

What to understand

Capture HTTP and HTTPS headers from the real edge, normal/error/authenticated paths, and each address family. Do not use `-k` for acceptance.

Record certificate SANs/expiry, TLS 1.2/1.3 handshakes, current obsolete-protocol rejection, and browser console behavior.

Inventory header ownership across Apache, application, CDN, and load balancer, including error responses.

System changes

  • No persistent change; creates network connections and an evidence record.

Syntax explained

curl --dump-header -
Prints response headers for exact value/cardinality review.
--output /dev/null
Avoids mixing response body with header evidence.
openssl s_client -servername
Tests the selected public certificate and protocol using SNI.
-tls1_2
Pins the handshake to TLS 1.2 for compatibility evidence.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

curl --silent --dump-header - --output /dev/null https://{{domain}}/ && echo | openssl s_client -connect {{domain}}:443 -servername {{domain}} -tls1_2 2>/dev/null | grep -E 'Protocol|Cipher|Verify return code'
Example output / evidence
HTTP/2 200
content-type: text/html
Protocol: TLSv1.2
Verify return code: 0 (ok)

Checkpoint: Checkpoint: baseline

Continue whenHTTPS and critical workflows are healthy, certificate verifies, protocol baseline is recorded, and every existing header has one known owner.

Stop whenThe baseline already contains certificate, redirect, duplicate-header, TLS, browser-console, or application failures.

If this step fails

curl and browser show different headers.

Likely causeA CDN, cache, authentication state, HTTP version, or service worker changes the path.

Safe checks
  • curl -v https://portal.example.com/
  • Compare DNS and response Via/Age headers

ResolutionIdentify and test the real serving layers and client states before assigning policy ownership.

Security notes

  • Baseline captures can contain cookies or internal headers; redact secrets before storing evidence.

Alternatives

  • Use an approved external scanner only as supplemental evidence; retain reproducible local commands and application tests.

Stop conditions

  • The baseline already contains certificate, redirect, duplicate-header, TLS, browser-console, or application failures.
02

command

Back up TLS and site configuration

caution

Archive enabled sites, SSL settings, and conf fragments before changing browser-enforced policy.

Why this step matters

Browser and TLS policy spans sites, modules, and conf fragments; a protected archive plus targeted fragment rollback prevents emergency guesswork.

What to understand

Archive available/enabled sites, conf, and SSL module configuration while preserving symlinks and ownership.

List and checksum the archive, document exact targeted rollback first, and retain current application release/CDN policy separately.

Configuration rollback cannot erase HSTS already cached by clients, so HTTPS continuity is part of rollback.

System changes

  • Creates `/root/apache-before-hardening.tgz` and checksum containing Apache site/module/conf state.

Syntax explained

tar -C /etc/apache2
Archives selected paths relative to the Apache configuration root.
-czf
Creates a compressed root-owned artifact.
sha256sum
Records integrity evidence for restore.
Command
sudo tar -C /etc/apache2 -czf /root/apache-before-hardening.tgz sites-available sites-enabled mods-available conf-available conf-enabled && sudo sha256sum /root/apache-before-hardening.tgz
Example output / evidence
189a...  /root/apache-before-hardening.tgz

Checkpoint: Checkpoint: backup

Continue whenThe protected archive lists every selected path, verifies by checksum, and has a rehearsed restore/configtest sequence.

Stop whenThe archive is incomplete, world-readable, lacks free space, or external edge/application policy has no rollback owner.

If this step fails

Archive omits enabled symlinks or custom includes.

Likely causeConfiguration lives outside the selected directories.

Safe checks
  • sudo apache2ctl -t -D DUMP_INCLUDES
  • sudo tar -tzf /root/apache-before-hardening.tgz | head -n 50

ResolutionInclude all authoritative paths in a new protected archive before editing.

Security notes

  • TLS configuration archives may reveal internal names and key paths; restrict access even when key bytes are not included.

Alternatives

  • Use an immutable config release and tested deployment rollback while preserving equivalent evidence.

Stop conditions

  • The archive is incomplete, world-readable, lacks free space, or external edge/application policy has no rollback owner.
03

command

Enable headers and SSL modules

caution

Enable first-party modules and confirm they load once.

Why this step matters

Header and TLS directives require their first-party modules, and confirming one loaded instance prevents debugging policy that Apache silently cannot apply.

What to understand

Enable only headers and ssl, then search for existing global/site policy before writing new fragments.

Module enablement affects all vhosts at capability level, while actual directives remain site-scoped.

Record Apache/OpenSSL package versions because supported protocol behavior follows maintained releases.

System changes

  • Enables headers and SSL module symlinks when not already active.

Syntax explained

a2enmod headers
Enables response/request header manipulation directives.
a2enmod ssl
Enables TLS protocol and certificate directives.
apache2ctl -M
Shows the effective loaded module set.
Command
sudo a2enmod headers ssl && sudo apache2ctl -M | grep -E 'headers|ssl'
Example output / evidence
headers_module (shared)
ssl_module (shared)

Checkpoint: Checkpoint: modules

Continue whenBoth modules load exactly once and configuration search reveals no unknown duplicate policy.

Stop whenModule loading fails, package origin is unexpected, or existing policy ownership is unknown.

If this step fails

Header directive is unknown despite a2enmod.

Likely causeApache is using another configuration root/binary or the module failed to load.

Safe checks
  • which apache2ctl
  • sudo apache2ctl -V
  • sudo apache2ctl -M | grep headers

ResolutionCorrect the managed Apache instance and module load error before adding policy.

Security notes

  • Module capability is global; actual trust-sensitive directives must still be scoped and reviewed.

Alternatives

  • Let the authoritative CDN/application emit a policy when origin-level ownership would duplicate it.

Stop conditions

  • Module loading fails, package origin is unexpected, or existing policy ownership is unknown.
04

config

Add conservative browser security headers

caution

Start with broadly compatible, site-scoped headers. Deploy Content-Security-Policy in report-only mode until application assets, frames, APIs, and inline code are inventoried. A single included directory keeps every hardening fragment inside the selected virtual host.

Why this step matters

Conservative invariant headers provide useful protection with limited compatibility risk, while report-only CSP gathers evidence before content blocking.

What to understand

Create a domain-specific include directory and insert one marked wildcard include inside the intended vhost; reruns must not duplicate it.

`nosniff` requires accurate Content-Type. Referrer Policy balances origin context and cross-origin privacy. Permissions Policy denies unused sensitive features.

The report-only CSP allows same-origin by default and denies plugins/base changes, but it is an initial observation policy rather than a final application policy.

Test 2xx, 3xx, 4xx, and 5xx responses because `Header always` applies across more response paths and can expose duplication.

System changes

  • Creates the site include directory/header fragment and inserts one marked IncludeOptional inside the selected vhost.

Syntax explained

Header always set
Sets one value across normal and selected error/internal response tables.
X-Content-Type-Options nosniff
Prevents selected MIME sniffing; correct server MIME types remain required.
Referrer-Policy strict-origin-when-cross-origin
Keeps full same-origin referrers while limiting cross-origin disclosure.
Permissions-Policy
Disables selected browser capabilities not used by this site.
Content-Security-Policy-Report-Only
Reports policy violations without enforcing blocks.
File /etc/apache2/sites-available/{{domain}}.d/40-security-headers.conf
Configuration
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

sudo install -d -m 0755 /etc/apache2/sites-available/{{domain}}.d && printf '<IfModule mod_headers.c>\n  Header always set X-Content-Type-Options "nosniff"\n  Header always set Referrer-Policy "strict-origin-when-cross-origin"\n  Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"\n  Header always set Content-Security-Policy-Report-Only "default-src '\''self'\''; object-src '\''none'\''; base-uri '\''self'\''"\n</IfModule>\n' | sudo tee /etc/apache2/sites-available/{{domain}}.d/40-security-headers.conf >/dev/null && sudo grep -q 'ONELINERS SECURITY INCLUDE' /etc/apache2/sites-available/{{domain}}.conf || sudo sed -i '\#</VirtualHost>#i\  # ONELINERS SECURITY INCLUDE\n  IncludeOptional /etc/apache2/sites-available/{{domain}}.d/*.conf' /etc/apache2/sites-available/{{domain}}.conf
Example output / evidence
Created site-scoped security header fragment and include.

Checkpoint: Checkpoint: headers

Continue whenOnly the intended site includes the directory and each conservative/report-only header appears once on representative statuses.

Stop whenInclude crosses tenants, MIME types are wrong, duplicate owner exists, or application feature requirements contradict the initial policy.

If this step fails

A header contains merged comma-separated policies.

Likely causeMultiple layers append/set the same field.

Safe checks
  • curl -sD - -o /dev/null https://portal.example.com/
  • sudo grep -R -n 'Header.*Content-Security' /etc/apache2

ResolutionDesignate one owner and remove duplicate append/set behavior before continuing.

Security notes

  • Do not treat report-only CSP as protection; review and secure report ingestion before using its data.

Alternatives

  • Emit a nonce/hash-aware CSP from the application while Apache retains only invariant headers.

Stop conditions

  • Include crosses tenants, MIME types are wrong, duplicate owner exists, or application feature requirements contradict the initial policy.
05

warning

Enable HSTS only after every subdomain is HTTPS-ready

danger

Begin without includeSubDomains or preload. HSTS is cached by browsers and cannot be instantly rolled back for clients that have seen it.

Why this step matters

A short site-only pilot teaches browsers to prefer HTTPS while limiting initial persistence; HSTS must wait until every required access path has durable HTTPS.

What to understand

Emit HSTS only when Apache knows the request is HTTPS. Test direct origin and edge so an upstream cannot accidentally add a longer policy.

One day is a pilot, not the final policy. Do not add includeSubDomains or preload until every subdomain and incident process is proven.

Removing the server header affects new responses only; browsers retain the previously seen policy until max-age expires.

System changes

  • Creates a site-scoped HSTS fragment with `max-age=86400` conditional on HTTPS.

Syntax explained

Strict-Transport-Security
Instructs supporting browsers to use HTTPS for future requests to this host.
max-age=86400
Caches the instruction for one day as an initial pilot.
expr=%{HTTPS} == 'on'
Prevents emission on plain HTTP responses.
File /etc/apache2/sites-available/{{domain}}.d/41-hsts.conf
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

printf '<IfModule mod_headers.c>\n  Header always set Strict-Transport-Security "max-age=86400" "expr=%{HTTPS} == '\''on'\''"\n</IfModule>\n' | sudo tee /etc/apache2/sites-available/{{domain}}.d/41-hsts.conf >/dev/null
Example output / evidence
Strict-Transport-Security: max-age=86400

Checkpoint: Checkpoint: hsts

Continue whenHSTS appears exactly once on HTTPS, never on HTTP, and the domain remains fully functional over HTTPS.

Stop whenAny required HTTP-only path, certificate/renewal risk, duplicate edge policy, or subdomain assumption is unresolved.

If this step fails

HSTS appears on another tenant.

Likely causeThe include is global or the wildcard directory is inserted outside the intended vhost.

Safe checks
  • sudo apache2ctl -S
  • sudo apache2ctl -t -D DUMP_INCLUDES
  • curl -sD - -o /dev/null https://other.example.com/ | grep -i strict

ResolutionRemove the fragment from global scope, parse/reload, and keep HTTPS healthy until affected client caches expire.

Security notes

  • HSTS increases dependence on certificate issuance and renewal; expiry monitoring and rollback readiness are mandatory.

Alternatives

  • Delay HSTS while retaining HTTP redirects until HTTPS operations and all clients are proven.

Stop conditions

  • Any required HTTP-only path, certificate/renewal risk, duplicate edge policy, or subdomain assumption is unresolved.
06

config

Set a modern protocol floor

caution

Disable legacy SSL/TLS protocol versions while leaving cipher selection to the maintained OpenSSL and distribution defaults unless a tested compliance profile requires more.

Why this step matters

An explicit modern protocol floor removes obsolete protocol negotiation while leaving algorithm selection to current maintained platform policy unless compliance requires more.

What to understand

Allow only TLS 1.2 and 1.3. Test all approved clients and both address families before activation.

Compression remains disabled; disabling session tickets changes performance/resumption behavior and should be measured.

Avoid an unaudited copied cipher string: TLS 1.3 cipher configuration differs and platform security updates evolve.

System changes

  • Creates a site-scoped mod_ssl fragment controlling protocols, compression, and session tickets.

Syntax explained

SSLProtocol -all
Starts from no enabled protocols.
+TLSv1.2 +TLSv1.3
Enables only the two reviewed modern protocol versions.
SSLCompression off
Disables TLS compression.
SSLSessionTickets off
Disables stateless session tickets; assess resumption/capacity impact.
File /etc/apache2/sites-available/{{domain}}.d/42-tls-policy.conf
Configuration
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

printf '<IfModule mod_ssl.c>\n  SSLProtocol -all +TLSv1.2 +TLSv1.3\n  SSLCompression off\n  SSLSessionTickets off\n</IfModule>\n' | sudo tee /etc/apache2/sites-available/{{domain}}.d/42-tls-policy.conf >/dev/null
Example output / evidence
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCompression off

Checkpoint: Checkpoint: tls policy

Continue whenTLS 1.2/1.3 handshakes and approved clients succeed; TLS 1.0/1.1 fail; certificate/latency remain healthy.

Stop whenAn approved client requires obsolete TLS, another scope conflicts, or performance/capacity changes are unexplained.

If this step fails

Apache configtest rejects TLSv1.3.

Likely causeApache/OpenSSL is older or linked against an implementation without TLS 1.3.

Safe checks
  • apache2ctl -v
  • openssl version -a
  • sudo apache2ctl configtest

ResolutionUse the supported maintained package set; do not claim TLS 1.3 until the actual stack supports it.

Security notes

  • Do not re-enable obsolete protocols globally for one legacy client; isolate or upgrade that client/service.

Alternatives

  • Apply an organization-maintained, versioned TLS compliance profile with automated compatibility tests.

Stop conditions

  • An approved client requires obsolete TLS, another scope conflicts, or performance/capacity changes are unexplained.
07

verification

Validate syntax and duplicate headers

read-only

Test Apache, reload, then count every security header to ensure application and proxy layers are not producing conflicting values.

Why this step matters

The complete parser plus post-reload header cardinality tests expose malformed quoting, bad include scope, and duplicate edge/application policy before acceptance.

What to understand

Parse and dump includes/vhosts before reload, then capture raw headers without following redirects or hiding certificate errors.

Count each header on success, redirect, not-found, and application-error responses. Values must be deliberate and singular.

Compare origin and public edge responses to identify CDN modifications and confirm HSTS is HTTPS-only.

System changes

  • Reloads Apache after successful syntax validation and creates normal test request/log records.

Syntax explained

apache2ctl configtest
Rejects invalid full configuration before activation.
curl --dump-header -
Shows exact header occurrences and values.
grep -iE
Selects intended policy fields for human cardinality review.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

sudo apache2ctl configtest && sudo systemctl reload apache2 && curl --silent --dump-header - --output /dev/null https://{{domain}}/ | grep -iE 'strict-transport|content-security|x-content-type|referrer-policy|permissions-policy'
Example output / evidence
Syntax OK
strict-transport-security: max-age=86400
x-content-type-options: nosniff

Checkpoint: Checkpoint: validate

Continue whenSyntax is valid, fragments remain site-scoped, and every intended header occurs exactly once with its reviewed value.

Stop whenParser, include/vhost scope, certificate, header count/value, HTTP-only behavior, or another tenant differs.

If this step fails

Configtest reports a quoted Header expression error.

Likely causeShell quoting produced malformed Apache syntax.

Safe checks
  • sudo sed -n '1,120p' /etc/apache2/sites-available/portal.example.com.d/41-hsts.conf
  • sudo apache2ctl configtest

ResolutionRestore or rewrite the small fragment directly, inspect it, and parse before reload.

Security notes

  • Never skip TLS verification or collapse multiple headers merely to make a test pass.

Alternatives

  • Validate on a canary vhost/address before public activation.

Stop conditions

  • Parser, include/vhost scope, certificate, header count/value, HTTP-only behavior, or another tenant differs.
08

verification

Test protocols and application paths

read-only

Verify TLS 1.2 and 1.3, reject TLS 1.0, and exercise login, upload, API, asset, and iframe paths before enforcing CSP.

Why this step matters

Protocol probes show transport behavior, but only real application workflows and browser consoles reveal CSP, MIME, referrer, feature, cookie, frame, and asset regressions.

What to understand

Prove TLS 1.2 and 1.3 with SNI and certificate verification; prove TLS 1.0/1.1 rejection rather than assuming directives took effect.

Exercise anonymous and authenticated critical paths, upload/download, API, assets, frames, logout/reset, redirects, and error pages.

Review report-only CSP events for real violations, browser extensions/noise, sensitive data, and source ownership before any enforcement.

Test approved automation and older enterprise clients; isolate legacy needs rather than weakening all sites.

System changes

  • No server change; creates test sessions, application records, and CSP report-only telemetry.

Syntax explained

s_client -tls1_2
Pins a positive TLS 1.2 handshake.
s_client -tls1
Attempts a negative obsolete-protocol handshake.
curl --fail criticalPath
Requires a representative public application success response.
Command
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

echo | openssl s_client -connect {{domain}}:443 -servername {{domain}} -tls1_2 2>/dev/null | grep 'Verify return code'; echo | openssl s_client -connect {{domain}}:443 -servername {{domain}} -tls1 2>&1 | grep -E 'alert protocol version|no protocols available' || true; curl --fail https://{{domain}}/{{criticalPath}}
Example output / evidence
Verify return code: 0 (ok)
no protocols available
critical path ok

Checkpoint: Checkpoint: compatibility

Continue whenModern protocols and every approved workflow/client pass, obsolete protocols fail, and CSP reports are understood without enforced blocking.

Stop whenAny approved workflow/client fails, obsolete TLS succeeds, certificate verification changes, or unexplained CSP violations remain.

If this step fails

A critical path works in curl but fails in the browser.

Likely causeCSP, CORS, frames, cookies, mixed content, service worker, or JS dependencies differ from a simple HTTP request.

Safe checks
  • Browser Network and Console panels
  • Review exact response headers and CSP report-only events

ResolutionFix the application dependency or minimally adjust the reviewed policy; keep CSP report-only until evidence is clean.

Security notes

  • Do not add broad `*`, unsafe-inline, or unsafe-eval reflexively; remove unsafe dependencies or use nonces/hashes where appropriate.

Alternatives

  • Canary enforcement on an internal/test domain with production-like content before the public host.

Stop conditions

  • Any approved workflow/client fails, obsolete TLS succeeds, certificate verification changes, or unexplained CSP violations remain.

Finish line

Verification checklist

Header policycurl --silent --dump-header - --output /dev/null https://{{domain}}/Each intended security header appears exactly once with the reviewed value.
TLS floorecho | openssl s_client -connect {{domain}}:443 -servername {{domain}} -tls1_2 2>/dev/null | grep 'Verify return code'TLS 1.2 succeeds with certificate verification code 0; obsolete protocols fail.

Recovery guidance

Common problems and safe checks

A security header appears twice with different values.

Likely causeApache, application, CDN, or another included file all emit the same policy.

Safe checks
  • curl -skD - -o /dev/null https://portal.example.com/
  • sudo grep -R -n 'Strict-Transport-Security\|Content-Security-Policy' /etc/apache2

ResolutionChoose one owner per header, remove the duplicate at the correct layer, parse, reload, and recount across success/error responses.

Assets or API calls would be blocked by CSP.

Likely causeThe report-only source list omits real origins, inline code, data/blob workers, fonts, frames, or connection endpoints.

Safe checks
  • Inspect browser developer-console CSP reports
  • Review sanitized report collector aggregates
  • Inventory application templates and network calls

ResolutionFix avoidable inline/third-party dependencies and add only the minimum intentional sources before a limited enforcement canary.

A user cannot return to HTTP after HSTS is disabled.

Likely causeThe browser cached the previously received max-age policy.

Safe checks
  • curl -sD - -o /dev/null https://portal.example.com/ | grep -i strict-transport
  • Check whether another edge still emits HSTS

ResolutionKeep HTTPS working until cached max-age expires; server-side removal cannot recall already delivered state.

TLS 1.2 fails after hardening while TLS 1.3 works.

Likely causeA protocol/cipher/profile conflict exists in another scope or the approved client lacks compatible algorithms.

Safe checks
  • sudo apache2ctl configtest
  • sudo grep -R -n 'SSLProtocol\|SSLCipherSuite' /etc/apache2
  • openssl s_client -connect portal.example.com:443 -servername portal.example.com -tls1_2

ResolutionFind the effective conflicting policy and reconcile it with current supported clients; do not re-enable obsolete protocols reflexively.

HSTS appears on HTTP or an unintended vhost.

Likely causeThe rule is global, the HTTPS expression is wrong, or a shared upstream emits it.

Safe checks
  • curl -sD - -o /dev/null http://portal.example.com/
  • sudo apache2ctl -S
  • sudo grep -R -n Strict-Transport-Security /etc/apache2

ResolutionScope the fragment inside the exact TLS vhost and retain the HTTPS expression; remove global duplication.

After the procedure

Alternatives and next steps

Consider these alternatives

  • Set content-aware CSP in the application while Apache owns only invariant transport and MIME/referrer headers.
  • Apply policy at a trusted CDN when it is the authoritative edge, but remove duplicate origin rules and preserve a tested direct-origin stance.
  • Use per-location or per-response rules for APIs/downloads that legitimately need different framing, MIME, or caching behavior.
  • Adopt a centrally maintained cryptographic compliance profile when required, with automated client compatibility and expiry tests.

Operate it safely

  • Create a CSP reporting endpoint or service with abuse controls, privacy review, aggregation, alerting, and a documented policy-owner workflow.
  • Promote CSP directive groups gradually on a canary path/domain, then enforce only after real workflows and error pages pass.
  • Increase HSTS max-age in stages after expiry monitoring, redirects, subdomain inventory, and incident response are proven.
  • Add automated external checks for header cardinality, certificate chain/expiry, TLS 1.2/1.3 success, obsolete-protocol failure, and critical application transactions.
  • Review cookie Secure/HttpOnly/SameSite, caching, CORS, framing needs, cross-origin isolation, and application-specific response policy with owners.

Reference

Frequently asked questions

Why not enable includeSubDomains and preload immediately?

Both expand the blast radius and preload is especially durable. Inventory every subdomain and prove long-term HTTPS operations first.

Why is CSP report-only?

It exposes real dependencies and violations without immediately blocking users; reports still require analysis before enforcement.

Should Apache or the application set headers?

Use one explicit owner per policy. Apache fits invariant edge policy; the application often understands content-specific CSP and cookies better.

Recovery

Rollback

Disable the individual hardening fragments, beginning with HSTS and CSP policy, then reload the previously validated configuration.

  1. Move the individual files from /etc/apache2/sites-available/{{domain}}.d out of the included directory, beginning with HSTS and CSP, then run apache2ctl configtest.
  2. Reload Apache and verify the application plus TLS behavior.
  3. Remember that clients which received HSTS retain it until max-age expires; keep HTTPS working during that period.
  4. Restore the archived configuration only if targeted fragment removal is insufficient.

Evidence

Sources and review

Verified 2026-07-25Review due 2026-10-23
Apache mod_headersofficialApache SSL/TLS encryptionofficialApache HTTP Server 2.4 documentationofficial