Banner image of Web server security – Part 3: TLS and security headers

Web server security – Part 3: TLS and security headers

In the second part of this series, we showed you how to harden your web server software. In this part, we introduce TLS cipher suites, talk about OCSP, and show you additional security-related HTTP response headers.

Always stay in the loop!
Subscribe to our RSS/Atom feed.

So far, we hardened the web server and already enabled TLS. However, there are even more security features, and TLS needs to be hardened.


  • hardened web server as configured in part 2
  • SSH client on your computer
  • knowledge about your website’s future content to set the HTTP response headers accordingly


To use HTTPS (and in some other cases), you need a digital certificate that you can obtain from Let’s Encrypt or other so-called “certificate authorities.” The basic idea of certificates is quite simple. Imagine the following scenario:

You want to buy alcoholic beverages, and the store clerk asks for your identity card to check your age. An authority issued your ID card. The store clerk trusts the authority which signed your ID card. Eventually, he trusts you due to the signed ID card you provided.

In our online world, servers provide certificates that are signed by certificate authorities. Clients trust certificate authorities. So, clients need to check if your server provides a valid certificate. The actual process is a bit more complicated, and there are several pitfalls (e.g., certificate revocation), but this is the basic idea.

As a server administrator, you can obtain three different types of certificates: DV (domain validated), OV (organization validated), and EV (extended validation). Nearly all common web browsers don’t differentiate between DV and OV certificates. EV certificates should be more trustworthy in theory since you must provide more information to prove your identity. However, there are more and more security experts who are convinced that EV certificates die soon. Besides, major web browsers like Chrome/Chromium and Firefox started to remove visual indicators for EV certificates. So, EV certificates look like DV/OV certificates for users.

Our advice is to obtain a free DV certificate from Let’s Encrypt. It is sufficient for most websites.

If you implemented part 2 of our series, you already got a DV certificate from Let’s Encrypt.


What is TLS?

TLS (Transport Layer Security) is one way to encrypt data in transit. The last two words are relevant here: “in transit.” TLS doesn’t protect data stored on your server (data at rest) or data that is processed by your server (data in use). TLS only protects data on its way from your server to clients and from clients to your server.

To use TLS, all parties involved must announce their supported cryptographic algorithms and agree on which algorithms to use. On the client side, this is automatically done by web browsers. On the server side, this is done by the web server. Then, only the client checks the certificate provided by the server for validity in most cases.

There is a risk here: As most web browsers support legacy algorithms, they can tell the web server to switch to weak legacy encryption. Switching from secure to insecure algorithms is a so-called downgrade attack. Due to this, it was good practice to enforce the server-side order of encryption algorithms. Nowadays, it is considered good practice to disable all legacy algorithms on the server side and to let clients choose the algorithms again. In this case, clients can switch to efficient algorithms if they come with limited hardware resources. Furthermore, all developers of significant web browsers already announced to drop support for legacy algorithms in the next few years.

Cryptographic parameters are grouped and called “cipher suites.” Cipher suites before TLS version 1.3 (published in August 2018) define:

  • key exchange algorithm (like ECDHE)
  • authenticity algorithm (like ECDSA or RSA)
  • encryption algorithm (like AES256-GCM or ChaCha20)
  • integrity algorithm (like SHA-384 or Poly1305)

Two examples are “ECDHE-ECDSA-CHACHA20-POLY1305” and “ECDHE-ECDSA-AES256-GCM-SHA384”. However, TLS 1.2 comes with many legacy cipher suites that are considered weak or insecure nowadays. As a server admin, you need to enable TLS, and you must also disable weak cipher suites.

An image showing cipher suites before TLS 1.3 and TLS 1.3 cipher suites.
Cipher suites before TLS 1.3 and TLS 1.3 cipher suites. (🔍 Zoom in)

TLS 1.3 deprecates many legacy algorithms and introduces additional security features since there are many known attacks against older versions of TLS and its predecessor SSL. TLS 1.3 cipher suites define:

  • encryption algorithm (like AES-256-GCM or ChaCha20)
  • integrity algorithm (like SHA-384)

Authenticity algorithms are defined in TLS 1.3 extensions, and the only relevant key exchange algorithm is ECDHE.

Do you need TLS?

Yes. Period. There are endless discussions about the need for TLS. We think that you always want at least authenticity and integrity: Being able to check that you are connected to the right server and to know that you receive data sent by the server. Encryption becomes important if sensitive information or personal data is exchanged.

Apache configuration

Go to “/etc/letsencrypt/” and open “options-ssl-apache.conf”. This file already includes the default TLS configuration of your server. Change the configuration to:

# Configuration for best compatibility
SSLProtocol +TLSv1.2

# Disable server-side preference if you don't offer any legacy cipher suites
SSLHonorCipherOrder off

# Disable TLS compression
SSLCompression off

# Disable TLS session tickets
SSLSessionTickets off

# Set curves to prime256v1 and secp384r1
# (X25519 isn't supported by the underlying OpenSSL version on Debian 9)
# (secp521r1 doesn't offer much more security)
SSLOpenSSLConfCmd Curves prime256v1:secp384r1
SSLOpenSSLConfCmd ECDHParameters secp384r1

Please note:

  • This configuration enables TLS 1.2 cipher suites that support FS and AEAD only. If you are already on Debian 10 (or run newer versions of Apache), you can additionally enable TLS 1.3 (“SSLProtocol +TLSv1.2” → “SSLProtocol +TLSv1.3 +TLSv1.2”).
  • To enable ChaCha20 cipher suites, you need OpenSSL 1.1.0 or newer. Otherwise, ChaCha20 cipher suites are enabled in your configuration file but aren’t supported by your web server.
  • Cipher suites that use ECDSA instead of RSA require an ECDSA certificate. By default, Let’s Encrypt only issues RSA certificates at the moment.

Save the file and restart Apache: sudo systemctl restart apache2. Check if there are any errors: systemctl status apache2.


What is OCSP?

The OCSP (Online Certificate Status Protocol) is essential for certificate revocation information. Imagine an attacker who accessed your server and stole your private key to issue new certificates or did something malicious. Some situations require you to revoke your certificate. The problem is that most clients never receive this information by default.

Years ago, there were Certificate Revocation Lists (CRL) that listed all revoked certificates. However, clients had to check for updates regularly, and lists became bigger and bigger. OCSP was invented, allowing clients to contact a third party (OCSP responder) and ask this trustworthy party if a certificate is still valid. This third party can log all requests of clients and track TLS connections of clients.

OCSP stapling fixes this: The server regularly contacts the OCSP responder and gets an authenticated OCSP response in advance. If a client connects to the server and asks for OCSP information, it gets the pre-authenticated OCSP response. The OCSP responder doesn’t learn about the connection, and the process is faster than raw OCSP. One problem remains: The client doesn’t know that a server can provide OCSP information.

The remaining problem brings OCSP Must-Staple into play. OCSP Must-Staple is a certificate extension that allows the client to learn about the presence of OCSP information during the TLS handshake.

Do you need OCSP?

Maybe. Only some web browsers support OCSP, and even fewer clients support OCSP stapling. However, support for OCSP stapling is required for Certificate Transparency, and Certificate Transparency becomes more and more popular.

Apache configuration

We already enabled OCSP stapling in part 2. However, we still have to enable OCSP Must-Staple that is embedded in our certificate.

A new certificate is needed. We need to tell Let’s Encrypt that we want “Must-Staple” embedded in the certificate by adding “–must-staple.” Furthermore, we must add “–force-renewal” to get a new certificate.

The full command is: sudo certbot renew --force-renewal --must-staple.

If you want an ECDSA certificate with OCSP Must-Staple extension, you have to configure the “csr.cnf” file before you request a certificate.

Certificate Transparency

Certificate Transparency (CT) is a more complex system that logs information about all certificates issued by trustworthy certificate authorities. Logging allows clients and other parties to validate certificates provided by servers.

Certificate authorities like Let’s Encrypt automatically embed SCTs (signed certificate timestamps) in certificates they issue. So, you don’t need to implement this. Your certificate should already contain SCTs.

Security-related HTTP response headers

We already enabled several security-relevant HTTP response headers in part 2. In this part, we discuss them and talk about even more headers. Please note that we don’t want to explain every detail of each header. There are several blogs and many websites that explain every single possibility to configure the headers. Some of these headers are also very new. Their format may be changed over time. Use search engines to learn about details, if necessary.

Current HTTP response headers

Most web browsers support the following HTTP response headers. You should understand the purpose of each header, and set it.

Content Security Policy

The Content Security Policy (Level 2) header tells clients which elements of the website they should load and which are forbidden. The important part here is that a client must support and follow the CSP to be effective. Some server admins enable CSP but allow “unsafe” rules that render the CSP header useless in some cases.

Before we look into an exemplary Content Security Policy, just a marginal note: You can also set the CSP in a <meta> element. The <meta> element defines “document-level metadata.” This CSP is only valid for content after the <meta> tag is processed. You need to consider this, especially when using dynamic content on your website. Furthermore, some CSP directives like “frame-ancestors” don’t work when set in the <meta> tag. We recommend that you set the CSP always as an HTTP response header in your web server configuration.

Let us analyze our CSP configured in part 2: Content-Security-Policy "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'".

  • “default-src ‘none’": Disallow everything except explicitly defined rules.
  • “img-src ‘self’": Load image files only from the server (no other domain names allowed).
  • “style-src ‘self’": Load style files (CSS) only from the server (no other domain names allowed).
  • “font-src ‘self’": Load font files only from the server (no other domain names allowed).
  • “base-uri ‘none’": Disable <base> tags.
  • “frame-ancestors ‘none’": Disable the possibility to embed your website in <embed> or <iframe> tags. Disabling frame-ancestors prevents clickjacking. Must match your X-Frame-Options header (see below).
  • “form-action ‘none’": Disables the possibility of <form> tags to initiate an action that effectively disables forms.
If you need inline elements (script/style), try to use "nonce" only. Nonces must be at least 128 bits long (before encoding), and you must use a CSPRNG for generating them. Currently, this is the most secure way to handle inline code.


The HTTP Strict Transport Security header tells clients to always use HTTPS connections for this domain name. HSTS becomes only useful when sent over HTTPS. Our configuration example in part 2 is: Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" env=HTTPS.

  • “max-age=31536000”: Tells the client to store this HSTS information for 31536000 seconds (roughly 1 year).
  • “includeSubDomains”: Tells the client that the HSTS directive is valid for all subdomains of this domain.
  • “preload”: This is an extension. Nowadays, all modern web browsers contain so-called “HSTS preload lists.” Domain names on these lists are always loaded via HTTPS, even if a client never visited the website before. You must set the parameter to be listed on preload lists.
  • “env=HTTPS”: Ensures that this header is sent over HTTPS only.


The referrer is part of an HTTP request. Clients send the referrer header to the server. There are different use cases for this. However, it can be used for tracking users. If you don’t need referrer information, you can disable it by setting: Referrer-Policy "no-referrer".


This header tells the web browser to follow MIME types defined in the Content-Type headers of server responses. It is only valid for <script> and <style> types.

We showed the following configuration: X-Content-Type-Options "nosniff". This blocks all requests of <style> without MIME type “text/css” and all requests of <script> without JS MIME types.

Legacy HTTP response headers

Current web browsers don’t support the following HTTP response headers or deprecate them soon. You only need to set these response headers if you want to support ancient web browsers. If you allow modern TLS 1.2 ciphers suites only, it is very likely that these old web browsers are already unable to connect to your web server. Due to this, we recommend skipping the following HTTP response headers.


The HTTP Public Key Pinning is a deprecated HTTP response header. Most web browsers dropped support for it. Simply do not use it.


The X-Frame-Options header serves the same purpose as the “frame-ancestors” directive of the CSP Level 2 (or newer levels). You only need it if you want to support legacy clients that don’t support Content Security Policy Level 2 directives. In this case, set it to X-Frame-Options "DENY".

“DENY” disables the possibility to embed your website using <embed> or <iframe> tags on a third-party website. The header prevents clickjacking. If you set this header, it must match the “frame-ancestors” directive of your Content Security Policy (see above).


The X-Xss-Protection header tells web browsers to act on reflected XSS. This header isn’t supported by all major web browsers (or they are about to drop support for it). If you still want to set it, set it to X-Xss-Protection "1; mode=block". This setting tells clients to stop rendering the website if reflected XSS is detected.

Upcoming and experimental HTTP response headers

There are more HTTP response headers, of course. Some of them are experimental; others are drafts. We implement some of these headers on for testing from time to time. Maybe, we add some of the following headers to the standard configuration if they become widely adopted by clients.

At the moment, most web browsers don't support the HTTP headers mentioned below. Clients without support simply ignore the headers. There is no additional protection if you set the following headers. On the other hand, you must carefully configure and test them. So, we don't recommend to set these headers at the moment.

Clear Site Data

Clear Site Data allows web developers to instruct a user-agent to clear a site’s locally stored data related to a host. This data contains cache (pages, script caches, etc.), cookies, storage (sessionStorage, localStorage, etc.) and executionContexts.

Content Security Policy Level 3

The third version of CSP adds the “manifest-src” directive, and switches from the deprecated “report-uri” directive to “report-to.” Besides, there are several changes to reporting violations and changes to directives.


The Cross Origin Opener Policy allows websites to request a new browsing context group to better isolate itself from other untrustworthy origins. The header aims to mitigate cross-window attacks and process-wide attacks. This header is highly experimental and not supported by clients.


Expect-Staple is a header defined by Scott Helme to monitor OCSP errors. This header can be used to discover misconfiguration or unreliability in your OCSP stapling deployment.


Expect-CT tells clients to check for valid Certificate Transparency information. This header also allows the monitoring of CT and detection of misconfiguration.


The Network Error Logging (NEL) header defines a mechanism enabling web applications to declare a reporting policy that can be used by the user-agent to report network errors for a given origin. The logged network errors even include problems that occurred before a connection was successfully established.

Permissions Policy

The upcoming Permissions Policy (formerly called Feature Policy) is like the Content Security Policy. However, you can use it to enable or disable certain features like “Geolocation.”


The Reporting-Endpoints header is defined in the Reporting API. It aims to provide a best-effort report delivery system that executes out-of-band with website activity and introduces new report types.

We regularly update this section to track the status of upcoming headers and modify their descriptions.

This article is part of the Web server security series.
Read other articles of this series.


Nearly all security-related headers require client-side support. There is no guarantee that a client follows your server-side directives. Nevertheless, you can enforce the most secure TLS setup at the moment and set strict rules for clients that can handle those headers. Keep in mind that good practices change over time. So, regularly recheck if your web server is still configured according to current good practices.

You also have to check if there are any issues due to HTTP response headers set by you. Some headers allow you to set “report-uri” or “report-to” directives to get error reports from clients. Furthermore, you can use the developer mode of most web browsers to see such errors in your web browser. However, this is more about technical misconfiguration and not about security issues.

In the next article, we introduce ModSecurity and Fail2ban.


  • Nov 3, 2020: Added information regarding a CSP in meta tags.
  • May 28, 2020: The Feature Policy is renamed to Permissions Policy.
  • Jul 27, 2019: Added information about legacy HTTP response headers (X-Xss-Protection and X-Frame-Options). Removed Subresource Integrity since this was never an HTTP response header.
  • Jul 14, 2019: Rewrote several sections due to the release of Debian 10 and part 0 of this series.
  • May 15, 2019: Added notes regarding experimental/upcoming headers. Clarified the difference between the current CSP Level 2 and the upcoming CSP Level 3.
  • Nov 30, 2018: Added Clear Site Data header.
  • Nov 11, 2018: Added NEL and Report-To headers.

Read also