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.
Reduce browser and transport attack surface without blindly applying headers that break application behavior.
- Apache HTTP Server 2.4.x
- OpenSSL 3.x
- 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.
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
- 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.
- 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.
- The client negotiates TLS 1.2 or 1.3 and Apache selects the intended SNI virtual host.
- The application or static handler produces a response, including its own content and cookies.
- Site-scoped mod_headers rules set conservative policy and report-only CSP, avoiding unrelated vhosts.
- Only an HTTPS request receives the pilot Strict-Transport-Security header.
- 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: accountSecurity 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.
verification
Capture current headers and TLS
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.
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'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.
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.
command
Back up TLS and site configuration
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.
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.tgz189a... /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.
sudo apache2ctl -t -D DUMP_INCLUDESsudo 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.
command
Enable headers and SSL modules
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.
sudo a2enmod headers ssl && sudo apache2ctl -M | grep -E 'headers|ssl'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.
which apache2ctlsudo apache2ctl -Vsudo 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.
config
Add conservative browser security headers
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.
/etc/apache2/sites-available/{{domain}}.d/40-security-headers.confValues 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}}.confCreated 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.
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.
warning
Enable HSTS only after every subdomain is HTTPS-ready
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.
/etc/apache2/sites-available/{{domain}}.d/41-hsts.confValues 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/nullStrict-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.
sudo apache2ctl -Ssudo apache2ctl -t -D DUMP_INCLUDEScurl -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.
config
Set a modern protocol floor
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.
/etc/apache2/sites-available/{{domain}}.d/42-tls-policy.confValues 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/nullSSLProtocol -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.
apache2ctl -vopenssl version -asudo 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.
verification
Validate syntax and duplicate headers
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.
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'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.
sudo sed -n '1,120p' /etc/apache2/sites-available/portal.example.com.d/41-hsts.confsudo 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.
verification
Test protocols and application paths
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.
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}}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.
Browser Network and Console panelsReview 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
curl --silent --dump-header - --output /dev/null https://{{domain}}/Each intended security header appears exactly once with the reviewed value.echo | 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.
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.
Inspect browser developer-console CSP reportsReview sanitized report collector aggregatesInventory 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.
curl -sD - -o /dev/null https://portal.example.com/ | grep -i strict-transportCheck 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.
sudo apache2ctl configtestsudo grep -R -n 'SSLProtocol\|SSLCipherSuite' /etc/apache2openssl 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.
curl -sD - -o /dev/null http://portal.example.com/sudo apache2ctl -Ssudo grep -R -n Strict-Transport-Security /etc/apache2
ResolutionScope the fragment inside the exact TLS vhost and retain the HTTPS expression; remove global duplication.
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.
- 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.
- Reload Apache and verify the application plus TLS behavior.
- Remember that clients which received HSTS retain it until max-age expires; keep HTTPS working during that period.
- Restore the archived configuration only if targeted fragment removal is insufficient.
Evidence