Secure Server Header Configuration for Ubuntu
Unfortunately, my experience shows that nowadays very few people think about server headers. Only a small number of websites can pass the HTTP Observatory test with a grade of B or higher. And here I’m not even focusing that much on the Content Security Policy (CSP) header, which is notoriously difficult to configure and can quickly turn into a headache when you start mapping out which resources are loaded from where.
Over the years, I’ve put together a header configuration that I personally rely on—and now I’d like to share it with you. Even more than that, I’ve prepared a complete Nginx configuration file that you can easily integrate into your setup. It’s designed to strengthen your site’s security without breaking functionality, helping you make your web application safer with minimal friction.
In this post, I’ll walk you through the reasoning behind these headers, why they matter, and how you can implement them in a practical, production-friendly way.
In this article, I’m providing a complete NGINX configuration file rather than just isolated snippets. I should also mention that the setup is specifically prepared for Ubuntu systems, following the default directory structure and conventions commonly used in Ubuntu-based NGINX installations.
# Hide Nginx version
server_tokens off;
# Headers
add_header X-Frame-Options "DENY";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; prelo ad";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Public-Key-Pins 'pin-sha256="base64+primary=="; pin-sha256="base64+ba ckup=="; max-age=5184000; includeSubDomains' always;
add_header Cross-Origin-Resource-Policy "same-origin";
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()";
# SSL
ssl_protocols TLSv1.2 TLSv1.3;
# Logging
access_log off;
# Buffer limitations
client_body_buffer_size 10K;
client_header_buffer_size 1k;
large_client_header_buffers 2 1k;
# Timeouts
client_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;
# GZip
gzip on;
gzip_comp_level 2;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain application/x-javascript text/xml text/css applicati on/xml;
# Static File Caching
location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
expires 365d;
}
# Limit Request Methods
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 444;
}To use it, simply save the file—for example, as /etc/nginx/secure.conf and then include it in your site’s configuration file located in /etc/nginx/sites-available. With just a small addition to your existing virtual host configuration, you can enable the enhanced security headers without restructuring your entire setup.
server {
# Custom global config that make server a bit secure
include /etc/nginx/secure.conf;
.....
}One more important note: make sure you enable at least HTTP/2 for your site. However, I recommend keeping the HTTP/2 directive inside your individual site configuration file rather than placing it in the shared security config, so you maintain flexibility and avoid unintended side effects across different virtual hosts.
listen 443 ssl http2;I’m sure this configuration can be further refined, modified, and extended to fit different environments or specific security requirements. However, this is the setup I’ve been using in my own projects, and it has proven to be reliable and practical. I hope it serves as a useful starting point—or even a solid solution—for others looking to strengthen their NGINX security configuration