I have a website secret.example.com, which contains information which must not be disclosed to third parties. In order to protect the information, TLS client authentication was chosen. Whether or not a client is authorized depends on them possessing a client certificate which is signed by the internal CA.

The Configuration

Here is the configuration for the relevant site in nginx. The nginx.conf file is the default file shipped with Ubuntu 20.04.01. I don't believe that this includes any vulnerabilities.

# HTTP Server
# Only redirect to HTTPS.
# Note: With HSTS and HSTS-Preload, this should be obsolete.
server {
    listen 80;
    server_name secret.example.com;
    return 301 https://$host$request_uri;

# HTTPS Server
# The meat of the package
server {

    # Enable HTTP2 and SSL/TLS
    listen 443 http2 ssl;

    # Sets the server name to one explicit name
    # Other server names on the same nginx instance are all static
    # (no regex, no wildcards)
    # "example.com" is the default server name
    server_name secret.example.com;

    # Include Let's Encrypt certificates
    # sets ssl_certificate, ssl_certificate_key and ssl_trusted_certificate
    include snippets/letsencrypt.conf;

    # Include security-related headers such as HSTS, CSP, X-Frame-Options, etc.
    include snippets/security-headers.conf;

    # Include SSL settings, such as ssl_protocols, ssl_ciphers,
    # ssl_prefer_server_ciphers, etc...
    # This is shared among all virtual servers
    include snippets/ssl-settings.conf;

    # SSL Client Verification
    # ssl_client_verify is set to "on",
    # meaning every request must have a valid client certificate
    # ...at least in theory
    ssl_client_certificate  /etc/ssl/nginx/secret.example.com/cert/ca.pem;
    ssl_verify_client      on;

    # Webroot is /var/www/secret.example.com
    root /var/www/secret.example.com;
    # If no other path is specified, try index.php
    index index.php;

    # Enable logging on a per-site basis
    access_log /var/log/nginx/secret.example.com.access.log;
    error_log /var/log/nginx/secret.example.com.error.log;

    # Either try to access the file directly, or use the @rewrite rule for MediaWiki
    location / {
        try_files $uri @rewrite;

    # The rewrite rule for more comfy URLS
    # This allows URLS like secret.example.com/Main_Page
    # instead of secret.example.com/index.php?title=Main_Page
    location @rewrite {
            rewrite ^/(.*)$ /index.php?title=$1&$args;

    # Pass PHP scripts to FastCGI server
    location ~ \.php$ {
            # This is the default snippet from the nginx package to pass php to php-fpm
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
            fastcgi_param SCRIPT_FILENAME $request_filename;

    # Static files eill either be served directly or via the index.php
    # Log for not-found files is disabled
    location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
            try_files $uri /index.php;
            expires max;
            log_not_found off;

    # This generates an empty 1x1 px gif at secret.example.com/_.gif
    # I'll be honest, I have no idea why this is here, but it's copied
    # straight from https://www.nginx.com/resources/wiki/start/topics/recipes/mediawiki/
    location = /_.gif {
            expires max;

    # Deny access to the cache at secret.example.com/cache/*
    location ^~ /cache/ {
            deny all;

The file ca.pem contains a self-signed certificate authority, created via the following openssl command:

 openssl req -new -x509 -nodes -days 1460 -key ca.key.pem > ca.pem

Client certificates would then be signed by this root CA.

What I have tried so far

  1. Send a certificate signed by the CA - This results, as expected, in the website being displayed correctly.
  2. Send no certificate - This results in an error returned by the server, claiming no client certificate was sent.
  3. Send a self-signed certificate by a CA with the same details as the real CA - This results in the error message "The SSL certificate error", which is not very descriptive, but still does not allow an attacker to see the confidential information.

My question

Is this configuration sufficient to enforce proper authorization? Or does an attacker have any possibility to still access the confidential information?

In order to scope the question further, the following scenarios are explicitly not in the scope of the question:

  • Vulnerabilities in nginx (however, "gotchas" in the configuration are in scope)
  • Disclosure of information through other sites (e.g. debug.example.com allowing LFI)
  • Direct attacks on the physical server
  • Attacks on the machine of a user, causing disclosure of a client certificate and private key
  • 2
    " ... , "gotchas" in the configuration are in scope ..." - The configuration protects only access to the resource via a specific domain+path - and it looks like it does it correctly. But it might still be possible that other parts of your nginx configuration allow less protected access to the same resource. This means just having this snippet is not sufficient to decide if the resource is sufficiently protected by nginx. Dec 30, 2020 at 7:04
  • @SteffenUllrich I've added the full configuration now. I didn't provide the full nginx.conf file because it's just the default from the nginx package and I don't think it's relevant. Snippets are not included, but I added what they do. I can include them if you need, but I doubt they're relevant
    – user163495
    Dec 30, 2020 at 14:12
  • 1
    Given that this is the only server configuration (i.e. no additional virtual hosts) this looks like properly protected to me. Dec 30, 2020 at 14:27
  • @SteffenUllrich There are other virtual hosts, but they do not allow access to secret.example.com and only serve static content.
    – user163495
    Dec 30, 2020 at 17:59


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy