Skip to content
Server hardening

Find vulnerabilities in web servers: 30+ hardening checks

Paste an Apache config block (.htaccess, VirtualHost, Directory, Location) or Nginx (server, location, upstream) and get a security-first context-aware audit: the tool runs native AST parsing (no regex grep) and applies 30+ checks on weak SSL/TLS, security headers with value validators for CSP (unsafe-inline, wildcard, report-uri), HSTS (max-age, includeSubDomains, preload), Permissions-Policy, forward secrecy, AEAD, HTTP/2, OCSP stapling, mod_security WAF, rate limiting on auth endpoints, HTTP->HTTPS redirect, Nginx add_header override semantics (the rule that wipes parent server headers when a location adds its own). Parser and rules run entirely in the browser: the config never leaves the page.

Drop a .conf, .htaccess, .nginx file here, or click to select (max 5 MB)

How to use the linter

  1. 1

    Get the config

    Apache: typical paths /etc/apache2/sites-enabled/*.conf, /etc/httpd/conf.d/*.conf, or the .htaccess at the site root. Nginx: /etc/nginx/sites-enabled/*, /etc/nginx/conf.d/*.conf, or the main nginx.conf. For assisted analysis: save the block to a .conf (or .htaccess) file and use the dropzone to upload it directly.

  2. 2

    Paste in the tool

    The tool auto-detects the dialect by counting canonical pattern occurrences (Apache: XML-like <...> tags, ServerTokens, SSLEngine; Nginx: server { }, location, add_header). If no pattern matches, it fails with an explicit diagnostic. Input never leaves the browser: parser and rules run in local JavaScript.

  3. 3

    Read the report

    Three-tier output. Detected dialect at the top. Findings ordered by severity: critical, high, medium, low, info. Aggregated score 0-100. For each issue: problem description, normative reference (Mozilla SSL Configurator, OWASP Secure Headers Project, historical CVEs), copy-paste-ready remediation.

  4. 4

    Export or print

    Copy report: plain text for internal tickets or hardening documents. Export for chatbot: complete markdown with context, detailed findings, and prompt template, to paste into ChatGPT/Gemini/Claude for remediation prioritization or fix-effort estimates. Browser Print for PDF export (CSS print-optimized, hides nav and CTA).

Why a context-aware linter is the right level of triage

Apache and Nginx configs look simple, but they are compositional. A directive can appear at top-level, inside an Apache <VirtualHost>, inside an Nginx server { }, inside a <Location> or a location { }: meaning changes with scope. Security headers declared in the wrong scope are formally present but do not apply to the page they were meant to protect. Modern ciphers declared at top-level get overridden by a legacy directive in a specific vhost. Regex grep does not see these subtleties: AST parsing does, and this version of the linter is based on a proper AST parser written in vanilla JS (~750 LOC total) producing a navigable tree of blocks and directives with line numbers.

What it actually checks. The linter applies 30+ checks across 5 families. SSL/TLS: obsolete protocols (SSLv2, SSLv3, TLSv1, TLSv1.1) and modern ones (TLSv1.2, TLSv1.3), weak ciphers (RC4, 3DES, EXPORT, NULL) vs strong ciphers, presence of forward secrecy (ECDHE/DHE), AEAD (GCM, ChaCha20-Poly1305), OCSP stapling. Security headers with value validator: this is the difference vs regex-based linters. CSP with unsafe-inline in script-src = effectively disabled, flagged medium; CSP with * wildcard, unsafe-eval, missing report-uri: separate findings each. HSTS with max-age < 6 months: medium. HSTS without includeSubDomains: low. HSTS without preload: info. Information disclosure: ServerTokens / server_tokens verbose, autoindex on, /server-status and /nginx_status not restricted. Availability and hardening: listen 80 without HTTPS redirect, rate limiting on auth endpoints (/login, /auth, /register), HTTP/2 enabled, mod_security WAF present. Nginx semantic overrides: the classical case where an add_header inside a location silently wipes all parent server add_header for that location, leaving the page without HSTS/CSP/X-Frame.

Difference between context-aware and regex-based linters. The previous linter on this tool ran regex grep on the entire config text: /Strict-Transport-Security/i.test(text). Result: HSTS declared inside <Location /admin> counted as HSTS present, even though the header was obviously not emitted outside /admin. Critical false negative. Same problem with Nginx add_header: presence in text does not imply application to the request. Same for protocols and ciphers: a directive valid in one vhost does not imply it applies to every other. The AST parser solves these problems at the root: every finding has precise context (which block, which line, with which arguments).

Operational privacy. A server config exposes the full structure of an infrastructure: hostnames, certificates, filesystem paths, internal backends, possible proxy caches, hidden admin paths. Uploading it to a third-party service for preliminary audit is a leak vector: the config leaves the perimeter, hits an external endpoint, gets processed and possibly logged. Here no network request leaves with config data: parser and rules run entirely in the browser. Verifiable by opening the DevTools Network panel during the lint.

When static triage is the right level. Typical scenarios. (a) Code review of a Pull Request modifying the Nginx config before merge: the linter covers 70-80% of hardening patterns and reports deterministically. (b) Pre-go-live audit of a new VPS: the freshly written DevOps config passes triage before production deploy. (c) Periodic self-check: the config running in production for two years is re-linted to catch regressions introduced by punctual changes. (d) Pre-engagement audit of a customer asking for a config review: thirty seconds of lint give the list of findings worth diving into with a serious hardening exercise.

Scope limit. The linter analyzes config text: it does not query the server runtime state, does not resolve Include / include against external files, does not validate X.509 certificates near expiry, does not verify the backend behind a proxy_pass actually answers HTTPS, does not parse logs for active attacks. For those aspects you need manual assessments, online scanners (SSLLabs, Mozilla Observatory) and continuous CVE monitoring. The linter is a fast triage, not a substitute.

What the tool checks, in detail

The linter is structured in three independent layers: AST parser, context-aware rules, value validators. All run in vanilla JS in the browser, zero dependencies.

AST parser (~750 LOC total). Two separate modules: apache-config-parser.js (~400 LOC, line-based with XML-like <VirtualHost> tags, backslash-continuation support, case-insensitive matching) and nginx-config-parser.js (~350 LOC, recursive descent with explicit tokenization of {, }, ;, quoted strings, comments). Output: AST with directive nodes (name + args + line) and block nodes (name + args + children + line). Helper API: findAll, findDirectives, findBlocks, walkContexts (visitor with ancestor chain), findInScope (direct children only). Validated by 26 inline fixtures (12 nginx + 14 apache, manually runnable from console: NginxConfigParser.runFixtures() / ApacheConfigParser.runFixtures()).

Context-aware rules. For Apache: ServerTokens / ServerSignature at top-level; for each <VirtualHost> separately, audit of SSL (SSLProtocol, SSLCipherSuite, SSLUseStapling), HSTS scoped to vhost, security headers, HTTP/2 (Protocols h2); <Directory /> with AllowOverride All; <Location /server-status> and /server-info with/without restrictive Require; presence of <IfModule mod_security2.c>. For Nginx: for each server { }, audit of SSL (ssl_protocols, ssl_ciphers, ssl_stapling), HSTS scoped to server, security headers, HTTP/2 (listen ... http2); autoindex on in any nested scope; location /nginx_status with/without allow/deny; rate limiting (limit_req) on auth-type locations (/login, /auth, /register, /password-reset); add_header override semantics.

Value validators. Three validators write targeted findings on actual header values. CSP: parsing the value into directives (script-src, default-src, report-uri, report-to); 'unsafe-inline' in script-src = medium (XSS protection collapsed); 'unsafe-eval' = medium; * wildcard = medium; missing report-uri/report-to = info. HSTS: parsing of max-age; missing = medium; < 6 months = medium; missing includeSubDomains = low; missing preload = info. Forward secrecy: presence of ECDHE/DHE patterns in ciphers; absence = medium. HTTP/2: missing in Apache Protocols h2 or Nginx listen ... http2 = info.

Nginx override semantics (special case). Nginx has a counterintuitive rule: if a location contains even a single add_header, ALL parent server add_header directives are ignored for that location. Practical result: HSTS, X-Frame, CSP declared at server level are NOT emitted on the specific location's response. The linter detects this configuration (server with add_header + location with add_header) and reports medium with the list of affected locations. Typical fix: repeat essential headers in every location that has overrides, or rewrite using more_set_headers from nginx-headers-more.

How to read a report

Aggregated score 0-100. Computed as 100 - sum(severity_weights), with weights: critical 30, high 15, medium 7, low 3, info 1. Reading: 80-100 green (reasonable posture, possibly small fine-tuning), 50-79 yellow (interventions plannable in upcoming sprints), 0-49 red (significant security debt, some interventions need immediate mitigation). The score does not measure backend quality behind the config, only the security posture visible from the manifest.

Finding severity. critical: confirmed vector leaving the server vulnerable to historical exploits (SSLv2/SSLv3 enabled, weak ciphers without explicit exclusion). Immediate patch. high: probably exploitable configuration (TLSv1/TLSv1.1 without exclusion, HSTS missing on SSL vhost, X-Frame missing, CSP missing, /server-status open, listen 80 without redirect). To be addressed in current sprint. medium: suboptimal practice that reduces defense in depth (missing forward secrecy, CSP with unsafe-inline, HSTS max-age < 6 months, autoindex on, AllowOverride All, add_header semantic override, rate limit missing on auth). Hardening backlog. low: detail to fix (HSTS without includeSubDomains, OCSP stapling missing, add_header without always). info: observational state (default ServerTokens, HSTS without preload, HTTP/2 disabled, mod_security not visible, no server block).

What to do, by priority. (1) For CRITICAL on SSL/TLS: immediately update SSLProtocol / ssl_protocols to TLSv1.2 TLSv1.3 only and SSLCipherSuite / ssl_ciphers to the Mozilla SSL Configurator recommendation (Modern profile for recent browsers, Intermediate for broad compatibility). (2) For HIGH header missing: add HSTS (max-age=31536000; includeSubDomains; preload), X-Frame-Options DENY (or frame-ancestors in CSP), CSP coherent with the frontend, X-Content-Type-Options nosniff. (3) For HIGH /server-status / /nginx_status open: restrict to 127.0.0.1 and/or management subnet. (4) For HIGH listen 80 no redirect: add Redirect permanent / https://... Apache or return 301 https://$host$request_uri; Nginx. (5) For MEDIUM CSP unsafe-inline: refactor the frontend to remove inline scripts, use nonce or hash; in inevitable residual cases (legacy analytics), accept the finding but document it. (6) For MEDIUM Nginx add_header override: repeat parent server headers in every location that adds its own.

Finding format. Each finding reports title, problem description, reference to the exact directive in the config (with line number when applicable), technical motivation, suggested remediation. Hand it to the team with the operational message: "apply the fix in the next deploy, or produce a written analysis of why the vector is not applicable to our architecture".

Summary table of applied checks

FamilyCheckMax severity
SSL/TLS protocolSSLv2 / SSLv3 enabled (Apache + Nginx)Critical
TLSv1 / TLSv1.1 enabledHigh
ssl_protocols / SSLProtocol unspecifiedMedium
Forward secrecy missing (no ECDHE/DHE)Medium
SSL/TLS cipherRC4 / 3DES / EXPORT / NULL not excludedHigh
OCSP stapling not enabledLow
HSTSHSTS missing on SSL vhostHigh
HSTS without max-ageMedium
HSTS max-age < 6 monthsMedium
HSTS without includeSubDomainsLow
HSTS without preloadInfo
CSPCSP missingHigh
CSP with 'unsafe-inline' / 'unsafe-eval'Medium
CSP with wildcard *Medium
CSP without report-uri / report-toInfo
Security headersX-Frame-Options missingHigh
X-Content-Type-Options missingMedium
Referrer-Policy / Permissions-Policy missingMedium
add_header without 'always' (Nginx)Low
Information disclosureServerTokens / server_tokens verboseInfo
autoindex / +Indexes enabledMedium
/server-status / /nginx_status not restrictedHigh
Availabilitylisten 80 without HTTPS redirectHigh
Rate limit missing on auth endpoints (Nginx)Medium
HTTP/2 not enabledInfo
Override / WAFadd_header override on location (Nginx)Medium
AllowOverride All on Directory / (Apache)Medium
mod_security WAF not visible (Apache)Info

Glossary

Technical terms used on this page, briefly explained.

VirtualHost (Apache) #
<VirtualHost> block that defines directives applied to a specific IP:port combination. An Apache config can have N VirtualHosts, each with different SSL, headers, certificate files. The linter analyzes every vhost separately.
server { } (Nginx) #
Nginx equivalent of Apache's VirtualHost. Defines directives for a listen + server_name combination. Nested inside http { } in the main config, or top-level in conf files included via include.
AST (Abstract Syntax Tree) #
Structured representation of the config after parsing. Every directive and block becomes a node with type, name, args, line, children. Allows answering scope and context questions that regex grep cannot see.
HSTS (Strict-Transport-Security) #
HTTP header instructing the browser to use HTTPS only for the domain for max-age seconds. Mitigation against TLS downgrade attacks. Best practice: max-age=31536000; includeSubDomains; preload and registration to Chrome's preload list (hstspreload.org).
CSP (Content-Security-Policy) #
HTTP header declaring which sources are authorized for script, style, img, connect, etc. Primary mitigation against reflected and DOM-based XSS. Main directives: default-src, script-src, style-src, img-src, connect-src, frame-ancestors, report-uri.
Forward secrecy #
TLS protocol property whereby the server's private key compromise does NOT allow decryption of past sessions. Guaranteed by ciphers with ephemeral key exchange (ECDHE, DHE). Static RSA ciphers lose it. Mozilla SSL Configurator Modern and Intermediate profiles both guarantee it.
AEAD (Authenticated Encryption with Associated Data) #
Cipher family combining encryption and authentication in a single operation, eliminating padding-oracle vulnerabilities. Examples: AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305. Recommended starting from TLS 1.2.
OCSP stapling #
TLS extension where the server presents the client with a CA-signed OCSP response, avoiding the client's OCSP query (privacy + performance). Directives: Apache SSLUseStapling on, Nginx ssl_stapling on; ssl_stapling_verify on;.
HTTP/2 / HTTP/3 #
Successor versions of HTTP with stream multiplexing, header compression (HPACK), server push (deprecated), QUIC over UDP for HTTP/3. Apache: Protocols h2 http/1.1. Nginx: listen 443 ssl http2 (pre-1.25 syntax), or http2 on; (1.25+ syntax).
mod_security / ModSecurity #
WAF (Web Application Firewall) for Apache and Nginx, based on pattern-matching rules on request/response (CRS = OWASP Core Rule Set). Mitigation against SQLi, XSS, RCE, scanner detection. Apache: <IfModule mod_security2.c>. Nginx: build with libmodsecurity.
Permissions-Policy #
Header that disables unused browser APIs (camera, microphone, geolocation, payment, USB, ambient-light, accelerometer, gyroscope). Reduces attack surface. Successor of Feature-Policy (deprecated). Example: camera=(), microphone=(), geolocation=().
X-Frame-Options / frame-ancestors #
X-Frame-Options (legacy): DENY | SAMEORIGIN | ALLOW-FROM origin. Mitigation against clickjacking. CSP frame-ancestors is the modern, more expressive version. Best practice: emit both, X-Frame as fallback for legacy browsers.
Referrer-Policy #
Header controlling what is sent in the Referer header during cross-origin navigation. Common values: strict-origin-when-cross-origin (modern default), no-referrer (maximum privacy). The variable browser default is an info leak.
rate limiting (Nginx) #
limit_req_zone defines rate-limit zones (rate-key + rate). limit_req inside a location applies the zone. Critical on authentication endpoints (login, signup, password reset) to resist brute force and credential stuffing.
add_header override (Nginx) #
Counterintuitive Nginx rule: if a location contains even a single add_header, the parent server add_header directives are ignored for that location. Workaround: repeat headers in every location, or use the headers-more-nginx-module (more_set_headers).
Mozilla SSL Configurator #
Official Mozilla tool generating ready SSL configs for Apache, Nginx, HAProxy, etc., choosing among three profiles: Modern (TLS 1.3 only, very recent browsers), Intermediate (TLS 1.2 + 1.3, broad compatibility, default recommended), Old (legacy, to avoid). URL: ssl-config.mozilla.org.

Frequently asked questions about the Apache/Nginx linter

I'm a sysadmin / DevOps with little specific experience on server hardening. Can I use this tool?
Yes. For each finding you get a problem description in operational language and copy-paste-ready remediation for the config. The tool's logic mirrors that of the official tools (Mozilla SSL Configurator, OWASP Secure Headers Project): the same checks a cyber expert would run, automated. For aspects the linter does not cover (cert near expiry, intermediate certificate validation, runtime monitoring) use SSLLabs and Mozilla Observatory in parallel.
What does AST-based mean and why is it better than a regex linter?
AST = Abstract Syntax Tree: the config is parsed into a tree of blocks and directives with precise scope. The linter knows that add_header X-Frame-Options DENY inside location /admin does NOT apply to the rest of the site. A regex linter would see the string X-Frame-Options anywhere in the text and say "present". That false negative has practical consequences: the header is formally there but is not emitted on the responses it was meant to protect. Same reasoning for HSTS, CSP, and the Nginx add_header override rule.
Can I test a .htaccess or do I need the full VirtualHost?
Both work. A .htaccess is a partial Apache config (inherited from the parent vhost): the linter accepts bare directives at top-level. A complete VirtualHost adds context (SSL port, certificates). For accurate audit prefer the full vhost because some checks (HSTS scoped to the SSL vhost, listen 80 redirect) require the block. If you paste only a .htaccess, the linter cannot decide whether you are on HTTPS or HTTP.
Does the Apache sample pass with score 100? I want to understand it as baseline.
The Apache sample includes two vhosts: one HTTP->HTTPS redirect, one HTTPS with modern SSL/TLS, HSTS preload, CSP with report-uri, X-Frame, X-Content-Type, Referrer, Permissions, OCSP stapling, HTTP/2 (Protocols h2), AllowOverride None on Directory. Expected score: 90-100. Possible drop: mod_security not visible (info, -1), no explicit ServerTokens declared (info, -1).
Nginx add_header override semantics: how do I really fix it?
Three approaches. (1) Repeat headers in every location: more boilerplate but deterministic, any sysadmin reading the config will understand it. (2) Use more_set_headers from headers-more-nginx-module: NOT subject to the override rule, global set that also applies to locations. Requires custom build or official package (Debian: libnginx-mod-http-headers-more-filter). (3) Refactor the location structure: collapse similar locations into one with map or if, reducing override surface.
CSP with 'unsafe-inline': when can it be accepted?
Ideally never in new projects: nonce-based or hash-based CSP are the modern baseline. In legacy cases: (a) frontend with thousands of inline event handlers requiring long refactor, accept unsafe-inline for the moment but document it in the config commit message; (b) analytics tools injecting inline scripts (Google Tag Manager pre-2023, some A/B testing tools), unsafe-inline is the fastest way. Partial mitigation: add 'self' + nonce for your scripts and accept unsafe-inline only as fallback whitelist.
HSTS preload: is it worth signing up to Chrome's preload list?
Yes for any stable web-only domain. The preload list integrates the domain into Chromium code (and derived browsers): the first visit to the domain is already forced to HTTPS, eliminating the first-visit downgrade attack window. Trade-off: once enrolled, you only resi HTTPS (no quick rollback to HTTP). Procedure: max-age=31536000 minimum, includeSubDomains mandatory, preload mandatory, submit at hstspreload.org.
/server-status and /nginx_status: should they be disabled or just restricted?
Restricted, not disabled: useful for monitoring (Nagios, Zabbix, custom dashboards). Typical restriction: Apache <Location /server-status> Require local </Location> or Require ip 10.0.0.0/8. Nginx location /nginx_status { stub_status on; allow 127.0.0.1; allow 10.0.0.0/8; deny all; }. Publicly exposed they leak internal hostnames, per-vhost traffic, request rate: information leakage useful for fingerprinting and detecting attack waves.
TLSv1 and TLSv1.1: should they be entirely disabled or is it OK to negotiate them for compatibility?
Entirely disabled for any new project. PCI DSS 4.0 requires TLSv1.2 minimum from mid-2024. Browser support: TLSv1.2 is supported by IE 11 onwards (Win 7+), Android 5+, iOS 5+. Remaining clients requiring TLSv1/TLSv1.1 are esoteric legacy clients (vintage enterprise printers, SCADA systems): if you have them, isolate them on a separate endpoint and declare TLSv1.2+ everywhere.
Forward secrecy: how critical is it in practice?
Critical for long-life data. Without forward secrecy, future private key compromise (server breach, key theft, court order) allows decryption of ALL past intercepted TLS sessions (recorded by a passive adversary like ISP or nation-state actor). With forward secrecy, past sessions stay encrypted. ECDHE/DHE ciphers guarantee it, static RSA does not. Best practice: Mozilla Intermediate SSLCipherSuite guarantees it, migrate to that.
HTTP/2 vs HTTP/3: should I enable both?
HTTP/2 is modern baseline (mature deploys since 2018, ~99% browser support). HTTP/3 (over QUIC) is next-gen but non-trivial deploy: requires UDP open at firewall, Apache only supports it via experimental mod_http2, Nginx requires --with-http_v3_module (1.25+). Pragmatic roadmap: HTTP/2 always, HTTP/3 when you have a real use case (mobile-heavy, devices on lossy connections).
mod_security: is it worth enabling?
Yes, for any publicly exposed site. Cost: ~5-10% CPU overhead with standard CRS. Benefit: automatic block of SQLi, XSS, RCE, recognizable scanners (nikto, sqlmap, dirb), enumeration, plain-text attack patterns. Caveat: initial configuration requires tuning to reduce false positives on the specific app. Modern alternative: cloud WAF (Cloudflare, AWS WAF, Sucuri). Costs but eliminates tuning.
Can I audit configs that use Include / include against external files?
The tool only lints what you paste. Apache Include and Nginx include point to external files: the browser cannot resolve them. Workaround: manually concatenate the files of interest and paste the resulting blob, or lint files one by one. Full audit requires server filesystem access, out of scope for a browser-only tool.
Can I audit the main /etc/nginx/nginx.conf file?
Yes, the parser correctly supports events { }, http { }, nested server { }, upstream, top-level directives. Typical findings on main nginx.conf: server_tokens off at http level, global limit_req_zone, ssl session cache. Per-server findings will appear for every server { } included (also via manually resolved includes).
Privacy: is the pasted config stored or tracked?
No. The tool runs entirely in the browser. No network request with the config: parser, context-aware rules, value validators, rendering, report generation - all in local JavaScript. Verifiable by opening the DevTools Network panel during the lint: zero outgoing requests with config data. The config is not saved in localStorage nor tracked in analytics.
Can I integrate the linter into a CI/CD pipeline?
For CI/CD use dedicated tools: nginx-config-formatter + Nginx configtest, Apache apachectl configtest, gixy (Yandex, Python) for Nginx security audit, apache-config-lint Ruby. Our tool is browser-based: great for interactive reviews, sub-optimal for pipelines. Valid use case: PR code review of a config change, the developer pastes the diff and triages before push.

Does your servers' config hold up under a serious security audit?

This linter covers static triage: protocols, ciphers, headers, semantic overrides, value validators. A professional review examines the full pipeline: runtime scan with SSLLabs and Mozilla Observatory, TCP kernel and system limits analysis, filesystem and permissions hardening, mod_security / cloud WAF configuration with CRS tuned to the app, integration with Dependabot and CI/CD, continuous CVE monitoring for server-side packages, application log audit for active attacks, deploy pipeline hardening. If the triage above showed critical or high findings, the problem already exists: the next step is understanding real impact, costs and timelines of remediation. I work on Apache, Nginx, Caddy, HAProxy backends with focus on production applications and regulatory adaptation (PCI DSS, NIS2, ISO 27001).

Server hardening audit

Want a realistic estimate for your project?

7-question wizard, 2 minutes, free. Output: range of person-days, rough price range, engagement recommendation. Reference rate 300 EUR/day. Built for custom backend projects, integrations, security audits or AI automation.

Get a free quote