Web Server audit fixes

TLS Configuration: fixing all 6 checks

The TLS audit reads the ssl directives in the nginx and Apache config and the certificate files they name, so a failure means traffic can be downgraded, decrypted or served on an expired certificate.

Certificate Expiry skips when the config names no certificate file the audit can actually open, which is what an unprivileged run against /etc/letsencrypt produces, and HTTPS Redirect skips when nothing answers on plain http://127.0.0.1/ for it to test. The other four skip only when no web server is detected.

6
checks in this audit
3
rated high severity
1
need root or sudo
~12s
automated run time

Transcribed from the web-server-security-tls-configuration audit script.

Every check

Find your finding

Listed in the order the audit runs them. Take the check name from the third field of the result line and open its page.

  • HTTPS Enabled

    Soon

    Checks whether the web server has any TLS configuration at all

    HighNo root3 results
  • TLS Protocols

    Soon

    Checks the configured protocol directives for TLS 1.0 and 1.1, and flags an absent explicit list

    HighNo root4 results
  • TLS Ciphers

    Soon

    Checks the configured cipher list for weak families that enable downgrade and decryption attacks

    MediumNo root4 results
  • Certificate Expiry

    Soon

    Checks every certificate referenced in the config for expiry within 30 days

    HighNeeds root3 results
  • HTTPS Redirect

    Soon

    Probes whether plain HTTP redirects to HTTPS rather than answering unencrypted

    MediumNo root3 results
  • OCSP Stapling

    Soon

    Checks whether OCSP stapling is enabled so clients skip a direct query to the CA

    LowNo root3 results
Shared script behaviour

How the TLS Configuration audit reads your server

Every check in this audit runs after the same preamble, and several of its results only make sense once you know what that preamble could and could not see.

Config-based checks read the effective nginx config (nginx -T as root) and the Apache config tree.

All web-server scripts share one preamble. It detects nginx, Apache (apache2/httpd, APROOT = /etc/apache2 or /etc/httpd), Caddy and lighttpd. NGT is nginx -T output (effective config with every include expanded) when root, otherwise a concatenation of nginx.conf, conf.d/*.conf, sites-enabled/*. APC is the concatenated Apache config tree (main file, ports.conf, conf.d, conf-enabled, sites-enabled, mods-enabled, conf.modules.d), comments stripped; APM is apachectl -M (root) or the mods-enabled/*.load names. WROOTS are all root/DocumentRoot values. A live probe curls https://127.0.0.1/, http://127.0.0.1/, http://127.0.0.1:8080/ with the first real server_name/ServerName as Host:. hdr NAME reads a response header; code URL returns the HTTP status. Caddy and lighttpd are detected but their configs are not parsed, so most checks on those hosts rely on the live probe only. Messages ending in "(no live response, config only)" or "(default vhost only - no server_name found to probe)" mean the probe could not be used.

Why checks skip

A SKIP is never a pass

A skipped check verified nothing at all. The most common cause is privilege: 1 of the 6 checks in this audit need root for a complete result. Before every run, each script works out which of four privilege modes it is in.

The four privilege modes an audit script can run in, and what each one means for checks that need root
ModeHow it is reachedWhat it means for your results
rootThe audit runs as UID 0.Every privileged branch runs directly. No check skips for lack of permission.
nopasssudo -n true succeeds, so the account has passwordless sudo.Privileged commands run through sudo -n.
passA sudo password was supplied in the CtrlOps audit settings and accepted.Privileged commands run through sudo -S.
noneNone of the above worked.CAN_ROOT=0. Every privileged branch returns SKIP with a "Need root" message rather than guessing.

If many checks skip with a “need root” message, run the audit as root, grant the audit account sudo (with or without NOPASSWD), or supply the sudo password in the CtrlOps audit settings.

FAQ

TLS Configuration questions

The check collects ssl_certificate and SSLCertificateFile paths from the config, keeps the absolute ones, and tests each file for readability before running openssl x509 -checkend against it. If none survives that test it reports no readable certificate files rather than guessing. The usual cause is an unprivileged run, because /etc/letsencrypt/live and archive are root-only. A certificate presented by Caddy, lighttpd or an upstream terminator is never in the parsed config at all, so it skips there too.
The check reads directives, not negotiated connections. It collects every ssl_protocols and SSLProtocol line in the effective config and fails if any one of them still lists SSLv2, SSLv3, TLSv1 or TLSv1.1, without working out which block wins for a given vhost. A stale line in the distribution's nginx.conf is the usual source, and it is worth fixing anyway: any server block that does not set its own list inherits it. Fix every directive the result named, not only the one on the vhost you care about.
Take them by severity: an expired or nearly expired certificate and TLS 1.0 or 1.1 still enabled are HIGH and change what a client can do today, with weak ciphers and a missing HTTPS redirect next. The OCSP stapling WARN is LOW and is the one to skip on most hosts, because Let's Encrypt stopped putting OCSP URLs in its certificates in May 2025 and shut its responders down in August 2025, which makes ssl_stapling on a no-op there. Whatever you change, run the config test first and reload rather than restart.
Audit your fleet

Run all 6 TLS Configuration checks, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

Start instantly· No credit card· No sneaky autorenewals