{"id":4906,"date":"2026-02-18T05:30:14","date_gmt":"2026-02-18T05:30:14","guid":{"rendered":"https:\/\/softcolontechnologies.com\/blogs\/?p=4906"},"modified":"2026-02-18T05:31:16","modified_gmt":"2026-02-18T05:31:16","slug":"how-to-configure-security-headers-to-protect-your-web-application","status":"publish","type":"post","link":"https:\/\/www.softcolon.com\/blogs\/how-to-configure-security-headers-to-protect-your-web-application\/","title":{"rendered":"How to Configure Security Headers to Protect Your Web Application"},"content":{"rendered":"<h2 class=\"text-3xl font-semibold mt-14 mb-8 \">Introduction: Why Security Headers Matter<\/h2>\n<p class=\" text-lg my-6\">When you build a web application and deploy it on the internet, you&#8217;re exposed to various attacks. Hackers use different techniques to steal data, hijack sessions, or redirect users to malicious sites. While firewalls and HTTPS provide basic protection, <strong>security headers<\/strong> are an additional layer of defense that run at the browser level.<\/p>\n<p class=\" text-lg my-6\">Think of security headers as instructions you send to every visitor&#8217;s browser, telling it: &#8220;Don&#8217;t trust scripts from unknown sources,&#8221; &#8220;Don&#8217;t display this page inside someone else&#8217;s website,&#8221; and &#8220;Always talk to me securely.&#8221; These are simple text messages included in every HTTP response, but they&#8217;re incredibly powerful.<\/p>\n<p class=\" text-lg my-6\"><strong>Caddy<\/strong>, a modern web server, makes adding these headers effortless. Unlike older servers like Apache or Nginx where you need to manually write complex configurations, Caddy provides a clean, simple syntax for implementing security headers consistently across your entire application.<\/p>\n<hr \/>\n<h2 class=\"text-3xl font-semibold mt-14 mb-8 \">Common Web Vulnerabilities Security Headers Protect Against<\/h2>\n<p class=\" text-lg my-6\">Before diving into the configuration, let&#8217;s understand the threats these headers defend against:<\/p>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">1. <strong>Cross-Site Scripting (XSS)<\/strong><\/h3>\n<p class=\" text-lg my-6\">An attacker injects malicious JavaScript code into your web page. When users visit, the script runs in their browsers, stealing login cookies or sensitive information.<\/p>\n<p class=\" text-lg my-6\"><strong>Example:<\/strong> A comment form that doesn&#8217;t validate input allows someone to post <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">&lt;script&gt;alert('hacked')&lt;\/script&gt;<\/code>, which then runs for everyone who views that comment.<\/p>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">2. <strong>Clickjacking<\/strong><\/h3>\n<p class=\" text-lg my-6\">An attacker embeds your website inside a hidden frame on their own malicious site. Users think they&#8217;re clicking a button on your site, but they&#8217;re actually clicking something invisible on the attacker&#8217;s page.<\/p>\n<p class=\" text-lg my-6\"><strong>Example:<\/strong> You see &#8220;Claim Your Prize!&#8221; on a site, but clicking it actually approves a bank transfer on your real bank&#8217;s website (which is hidden underneath).<\/p>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">3. <strong>MIME Type Sniffing<\/strong><\/h3>\n<p class=\" text-lg my-6\">Older browsers would guess the file type based on content, not just the filename. An attacker could upload a malicious script with a <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">.jpg<\/code> extension, and the browser might execute it as JavaScript anyway.<\/p>\n<p class=\" text-lg my-6\"><strong>Example:<\/strong> Upload a file named <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">photo.jpg<\/code> containing JavaScript code. Some browsers might run it as a script instead of displaying it as an image.<\/p>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">4. <strong>Man-in-the-Middle (MITM) Attacks<\/strong><\/h3>\n<p class=\" text-lg my-6\">An attacker intercepts your connection and reads or modifies data in transit. This happens when someone connects to unsecured HTTP instead of HTTPS.<\/p>\n<p class=\" text-lg my-6\"><strong>Example:<\/strong> Using public WiFi at a caf\u00e9, an attacker intercepts your login credentials when you visit an HTTP website.<\/p>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">5. <strong>Information Leakage via Referrer<\/strong><\/h3>\n<p class=\" text-lg my-6\">When you click a link to another site, that site can see which page you came from (the referrer). This can leak sensitive information.<\/p>\n<p class=\" text-lg my-6\"><strong>Example:<\/strong> If you click a link from a private healthcare website to a search engine, that search engine knows you visited a healthcare site.<\/p>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">6. <strong>Unauthorized Browser Feature Access<\/strong><\/h3>\n<p class=\" text-lg my-6\">Websites might request access to your camera, microphone, or location without your explicit consent.<\/p>\n<p class=\" text-lg my-6\"><strong>Example:<\/strong> A malicious website silently accesses your webcam to spy on you.<\/p>\n<hr \/>\n<h2 class=\"text-3xl font-semibold mt-14 mb-8 \">Understanding the Security Headers Configuration<\/h2>\n<p class=\" text-lg my-6\">Here&#8217;s the complete Caddy configuration with detailed explanations:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\">example.<span class=\"hljs-property\">com<\/span> {\n    header {\n        <span class=\"hljs-title class_\">Strict<\/span>-<span class=\"hljs-title class_\">Transport<\/span>-<span class=\"hljs-title class_\">Security<\/span> <span class=\"hljs-string\">\"max-age=31536000; includeSubDomains; preload\"<\/span>\n        X-<span class=\"hljs-title class_\">Content<\/span>-<span class=\"hljs-title class_\">Type<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"nosniff\"<\/span>\n        X-<span class=\"hljs-title class_\">Frame<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"DENY\"<\/span>\n        X-<span class=\"hljs-variable constant_\">XSS<\/span>-<span class=\"hljs-title class_\">Protection<\/span> <span class=\"hljs-string\">\"0\"<\/span>\n        <span class=\"hljs-title class_\">Referrer<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"strict-origin-when-cross-origin\"<\/span>\n        <span class=\"hljs-title class_\">Permissions<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"geolocation=(), microphone=(), camera=()\"<\/span>\n    }\n\n    reverse_proxy <span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3000<\/span>\n}\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">What Each Header Does<\/h3>\n<h4>1. <strong>Strict-Transport-Security (HSTS)<\/strong><\/h4>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-string\">\"max-age=31536000; includeSubDomains; preload\"<\/span>\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>What it does:<\/strong> Forces all communication with your website to use HTTPS (secure connection), never HTTP (unsecure).<\/p>\n<p class=\" text-lg my-6\"><strong>Why it matters:<\/strong> Prevents attackers from intercepting data by forcing users to upgrade to HTTPS automatically, even if they accidentally type <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">http:\/\/<\/code> instead of <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">https:\/\/<\/code>.<\/p>\n<p class=\" text-lg my-6\"><strong>Breaking down the values:<\/strong><\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">max-age=31536000<\/code>: Remember this rule for 365 days (31,536,000 seconds)<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">includeSubDomains<\/code>: Apply this rule to all subdomains (api.example.com, blog.example.com, etc.)<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">preload<\/code>: Add your site to browsers&#8217; HSTS preload list, so this rule is built into the browser before users even visit<\/p>\n<\/li>\n<\/ul>\n<p class=\" text-lg my-6\"><strong>Real-world example:<\/strong> Without this header, if someone types <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">http:\/\/example.com<\/code>, they might land on an unsecured connection momentarily, giving attackers a window. With HSTS, the browser says &#8220;I know example.com requires HTTPS&#8221; and automatically upgrades the connection.<\/p>\n<hr \/>\n<h4>2. <strong>X-Content-Type-Options: nosniff<\/strong><\/h4>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-string\">\"nosniff\"<\/span>\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>What it does:<\/strong> Tells the browser &#8220;Trust the file type I tell you, don&#8217;t guess.&#8221;<\/p>\n<p class=\" text-lg my-6\"><strong>Why it matters:<\/strong> Prevents browsers from misinterpreting files. If you serve a file as an image, it won&#8217;t be executed as a script.<\/p>\n<p class=\" text-lg my-6\"><strong>Technical detail:<\/strong> Older browsers had &#8220;MIME sniffing&#8221; where they&#8217;d inspect file content to guess the actual type, overriding what the server declared. This header disables that behavior.<\/p>\n<p class=\" text-lg my-6\"><strong>Real-world example:<\/strong> Without this header, an attacker could upload a malicious JavaScript file named <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">vacation.jpg<\/code>. Some browsers might execute it as a script anyway. With this header, the browser trusts your declaration that it&#8217;s an image.<\/p>\n<hr \/>\n<h4>3. <strong>X-Frame-Options: DENY<\/strong><\/h4>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-string\">\"DENY\"<\/span>\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>What it does:<\/strong> Prevents your website from being displayed inside an iframe (a frame within another website).<\/p>\n<p class=\" text-lg my-6\"><strong>Why it matters:<\/strong> Protects against clickjacking attacks where attackers hide your site in a frame and trick users into clicking things unknowingly.<\/p>\n<p class=\" text-lg my-6\"><strong>Alternative values you might see:<\/strong><\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">DENY<\/code>: Never allow this site in an iframe (most secure)<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">SAMEORIGIN<\/code>: Allow iframes only from your own domain<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">ALLOW-FROM https:\/\/trusted-site.com<\/code>: Allow iframes only from a specific trusted domain<\/p>\n<\/li>\n<\/ul>\n<p class=\" text-lg my-6\"><strong>Real-world example:<\/strong> Without this header, an attacker could create a page with your banking site loaded in a hidden iframe, then overlay fake buttons. Users think they&#8217;re clicking your site but are actually interacting with the attacker&#8217;s page. With <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">DENY<\/code>, this becomes impossible.<\/p>\n<hr \/>\n<h4>4. <strong>X-XSS-Protection: 0<\/strong><\/h4>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-string\">\"0\"<\/span>\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>What it does:<\/strong> Disables the browser&#8217;s built-in XSS protection feature.<\/p>\n<p class=\" text-lg my-6\"><strong>Why it&#8217;s set to 0 (counterintuitive):<\/strong> Modern browsers (Chrome, Firefox, Safari, Edge) have moved away from this header because their built-in XSS protection can be bypassed and actually causes more problems than it solves. Setting it to <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">\"0\"<\/code> explicitly disables this feature and tells browsers to rely on other protections instead (like Content-Security-Policy, which we&#8217;ll cover later).<\/p>\n<p class=\" text-lg my-6\"><strong>Modern alternative:<\/strong> Instead of relying on <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">X-XSS-Protection<\/code>, modern sites use the <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">Content-Security-Policy<\/code> (CSP) header, which provides stronger XSS protection:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-title class_\">Content<\/span>-<span class=\"hljs-title class_\">Security<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'\"<\/span>\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\">This tells the browser: &#8220;Only load scripts from my own domain (<code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">'self'<\/code>), nothing else.&#8221;<\/p>\n<hr \/>\n<h4>5. <strong>Referrer-Policy: strict-origin-when-cross-origin<\/strong><\/h4>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-string\">\"strict-origin-when-cross-origin\"<\/span>\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>What it does:<\/strong> Controls how much information your site shares about where users came from when they navigate to other websites.<\/p>\n<p class=\" text-lg my-6\"><strong>Why it matters:<\/strong> Prevents leaking sensitive information through the referrer URL. For example, if your referrer URL contains a user ID or session token, other sites could see it.<\/p>\n<p class=\" text-lg my-6\"><strong>How it works:<\/strong><\/p>\n<p class=\" text-lg my-6\">When you click a link, your browser sends a &#8220;Referrer&#8221; header to the destination site, saying &#8220;I came from example.com\/private-health-records.&#8221; This can leak sensitive information.<\/p>\n<p class=\" text-lg my-6\">The <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">strict-origin-when-cross-origin<\/code> value means:<\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>Within your own domain:<\/strong> Send full referrer URL (e.g., <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">example.com\/page1<\/code>)<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>To other domains:<\/strong> Send only the domain name, not the full path (e.g., just <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">example.com<\/code>, not <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">example.com\/private-data<\/code>)<\/p>\n<\/li>\n<\/ul>\n<p class=\" text-lg my-6\"><strong>Real-world example:<\/strong> Without this header, if you visit <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">example.com\/medical-records\/patient-123<\/code> and click a link to Google, Google can see that full URL in the referrer. With this header, Google only sees <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">example.com<\/code>, not the sensitive path.<\/p>\n<p class=\" text-lg my-6\"><strong>Other options:<\/strong><\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">no-referrer<\/code>: Don&#8217;t send any referrer information (most private, but breaks some analytics)<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">same-origin<\/code>: Only send referrer within your own domain<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">strict-origin-when-cross-origin<\/code>: What we recommend (good balance of privacy and functionality)<\/p>\n<\/li>\n<\/ul>\n<hr \/>\n<h4>6. <strong>Permissions-Policy (formerly Feature-Policy)<\/strong><\/h4>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-string\">\"geolocation=(), microphone=(), camera=()\"<\/span>\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>What it does:<\/strong> Restricts which browser features websites can access.<\/p>\n<p class=\" text-lg my-6\"><strong>Why it matters:<\/strong> Prevents malicious websites (or compromised third-party scripts) from silently accessing your camera, microphone, location, or other sensitive hardware.<\/p>\n<p class=\" text-lg my-6\"><strong>How to read this:<\/strong><\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">geolocation=()<\/code>: No website can access your location (empty parentheses mean nobody)<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">microphone=()<\/code>: No website can access your microphone<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">camera=()<\/code>: No website can access your camera<\/p>\n<\/li>\n<\/ul>\n<p class=\" text-lg my-6\"><strong>Real-world example:<\/strong> Even if a website&#8217;s JavaScript is compromised, it can&#8217;t request access to your camera without your permission. And if a third-party ad script tries to be sneaky, this policy blocks it entirely.<\/p>\n<p class=\" text-lg my-6\"><strong>More granular options:<\/strong><\/p>\n<p class=\" text-lg my-6\">If you want to allow specific features:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-title class_\">Permissions<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"geolocation=(self), camera=(self), microphone=(*)\"<\/span>\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">geolocation=(self)<\/code>: Allow only your own domain to access location<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">camera=(self)<\/code>: Allow only your own domain to access camera<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">microphone=(*)<\/code>: Allow any domain (usually not recommended)<\/p>\n<\/li>\n<\/ul>\n<hr \/>\n<h2 class=\"text-3xl font-semibold mt-14 mb-8 \">How to Apply This Configuration<\/h2>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">Step 1: Access Your Caddy Configuration File<\/h3>\n<p class=\" text-lg my-6\">Caddy stores its configuration in a file called <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">Caddyfile<\/code>. Depending on how you installed Caddy, this file is usually located at:<\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>Linux:<\/strong> <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">\/etc\/caddy\/Caddyfile<\/code><\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>Docker:<\/strong> Inside the container at <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">\/etc\/caddy\/Caddyfile<\/code><\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>Manual installation:<\/strong> Wherever you specified it to be<\/p>\n<\/li>\n<\/ul>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">Step 2: Add the Security Headers Block<\/h3>\n<p class=\" text-lg my-6\">Open your Caddyfile and find your domain configuration. If you don&#8217;t have one yet, create it:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\">example.<span class=\"hljs-property\">com<\/span> {\n    header {\n        <span class=\"hljs-title class_\">Strict<\/span>-<span class=\"hljs-title class_\">Transport<\/span>-<span class=\"hljs-title class_\">Security<\/span> <span class=\"hljs-string\">\"max-age=31536000; includeSubDomains; preload\"<\/span>\n        X-<span class=\"hljs-title class_\">Content<\/span>-<span class=\"hljs-title class_\">Type<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"nosniff\"<\/span>\n        X-<span class=\"hljs-title class_\">Frame<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"DENY\"<\/span>\n        X-<span class=\"hljs-variable constant_\">XSS<\/span>-<span class=\"hljs-title class_\">Protection<\/span> <span class=\"hljs-string\">\"0\"<\/span>\n        <span class=\"hljs-title class_\">Referrer<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"strict-origin-when-cross-origin\"<\/span>\n        <span class=\"hljs-title class_\">Permissions<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"geolocation=(), microphone=(), camera=()\"<\/span>\n    }\n\n    reverse_proxy <span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3000<\/span>\n}\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">Step 3: Test the Configuration<\/h3>\n<p class=\" text-lg my-6\">Before reloading, verify your syntax is correct:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-bash whitespace-pre-wrap break-words text-gray-300\">caddy validate\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">Step 4: Reload Caddy<\/h3>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-bash whitespace-pre-wrap break-words text-gray-300\">caddy reload\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\">This reloads the configuration without stopping your web server (zero downtime).<\/p>\n<hr \/>\n<h2 class=\"text-3xl font-semibold mt-14 mb-8 \">Applying Headers Across Multiple Sites (DRY Principle)<\/h2>\n<p class=\" text-lg my-6\">If you&#8217;re hosting multiple websites or have many subdomains, you don&#8217;t want to repeat the same security headers for each one. Caddy provides a way to define reusable configuration blocks called <strong>snippets<\/strong>.<\/p>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">Using Snippets for Code Reuse<\/h3>\n<p class=\" text-lg my-6\">Create a snippet at the top of your Caddyfile:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\">(security_headers) {\n    header {\n        <span class=\"hljs-title class_\">Strict<\/span>-<span class=\"hljs-title class_\">Transport<\/span>-<span class=\"hljs-title class_\">Security<\/span> <span class=\"hljs-string\">\"max-age=31536000; includeSubDomains; preload\"<\/span>\n        X-<span class=\"hljs-title class_\">Content<\/span>-<span class=\"hljs-title class_\">Type<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"nosniff\"<\/span>\n        X-<span class=\"hljs-title class_\">Frame<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"DENY\"<\/span>\n        X-<span class=\"hljs-variable constant_\">XSS<\/span>-<span class=\"hljs-title class_\">Protection<\/span> <span class=\"hljs-string\">\"0\"<\/span>\n        <span class=\"hljs-title class_\">Referrer<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"strict-origin-when-cross-origin\"<\/span>\n        <span class=\"hljs-title class_\">Permissions<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"geolocation=(), microphone=(), camera=()\"<\/span>\n    }\n}\n\nexample.<span class=\"hljs-property\">com<\/span> {\n    <span class=\"hljs-keyword\">import<\/span> security_headers\n    reverse_proxy <span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3000<\/span>\n}\n\napi.<span class=\"hljs-property\">example<\/span>.<span class=\"hljs-property\">com<\/span> {\n    <span class=\"hljs-keyword\">import<\/span> security_headers\n    reverse_proxy <span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3001<\/span>\n}\n\nblog.<span class=\"hljs-property\">example<\/span>.<span class=\"hljs-property\">com<\/span> {\n    <span class=\"hljs-keyword\">import<\/span> security_headers\n    reverse_proxy <span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3002<\/span>\n}\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>Benefits:<\/strong><\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Write the security headers once<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Import them everywhere you need them<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Update once, and all sites get the new headers<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Reduces mistakes from copy-pasting<\/p>\n<\/li>\n<\/ul>\n<hr \/>\n<h2 class=\"text-3xl font-semibold mt-14 mb-8 \">Common Mistakes to Avoid<\/h2>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">Mistake 1: Using an IP Address Instead of a Domain<\/h3>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"># \u274c <span class=\"hljs-variable constant_\">WRONG<\/span>\n<span class=\"hljs-number\">192.168<\/span><span class=\"hljs-number\">.1<\/span><span class=\"hljs-number\">.100<\/span> {\n    header {\n        <span class=\"hljs-title class_\">Strict<\/span>-<span class=\"hljs-title class_\">Transport<\/span>-<span class=\"hljs-title class_\">Security<\/span> <span class=\"hljs-string\">\"max-age=31536000\"<\/span>\n    }\n}\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>Why this fails:<\/strong> TLS certificates (HTTPS) require a valid domain name, not an IP address. You can&#8217;t get an SSL certificate for <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">192.168.1.100<\/code>.<\/p>\n<p class=\" text-lg my-6\"><strong>Solution:<\/strong> Always use a domain name. If you&#8217;re testing locally, use a domain like <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">localhost<\/code> or set up a local domain in your <code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">\/etc\/hosts<\/code> file:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-bash whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-comment\"># Add to \/etc\/hosts<\/span>\n127.0.0.1 myapp.local\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\">Then in Caddyfile:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\">myapp.<span class=\"hljs-property\">local<\/span> {\n    header {\n        <span class=\"hljs-title class_\">Strict<\/span>-<span class=\"hljs-title class_\">Transport<\/span>-<span class=\"hljs-title class_\">Security<\/span> <span class=\"hljs-string\">\"max-age=31536000\"<\/span>\n    }\n    reverse_proxy <span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3000<\/span>\n}\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<hr \/>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">Mistake 2: Running Node.js Directly to the Internet<\/h3>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"># \u274c <span class=\"hljs-variable constant_\">WRONG<\/span> - <span class=\"hljs-title class_\">Exposing<\/span> <span class=\"hljs-title class_\">Node<\/span>.<span class=\"hljs-property\">js<\/span> app directly\n# <span class=\"hljs-title class_\">No<\/span> reverse proxy, app handles <span class=\"hljs-variable constant_\">HTTP<\/span> directly\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>Why this is dangerous:<\/strong><\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Your Node.js app handles raw HTTP requests (slower)<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">No centralized place to add headers<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Security headers aren&#8217;t applied<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Performance suffers<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">If Node.js crashes, your site is down immediately<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Each Node.js instance needs its own port, making scaling complicated<\/p>\n<\/li>\n<\/ul>\n<p class=\" text-lg my-6\"><strong>Correct approach:<\/strong> Always put Caddy in front:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"># \u2705 <span class=\"hljs-variable constant_\">CORRECT<\/span>\nexample.<span class=\"hljs-property\">com<\/span> {\n    header {\n        <span class=\"hljs-title class_\">Strict<\/span>-<span class=\"hljs-title class_\">Transport<\/span>-<span class=\"hljs-title class_\">Security<\/span> <span class=\"hljs-string\">\"max-age=31536000; includeSubDomains; preload\"<\/span>\n        X-<span class=\"hljs-title class_\">Content<\/span>-<span class=\"hljs-title class_\">Type<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"nosniff\"<\/span>\n        X-<span class=\"hljs-title class_\">Frame<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"DENY\"<\/span>\n    }\n    \n    reverse_proxy <span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3000<\/span>\n}\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>Benefits of this setup:<\/strong><\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Caddy handles HTTPS and certificates<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Caddy applies headers consistently<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Node.js focuses on business logic<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Easy to restart Node.js without downtime<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Load balance across multiple Node.js instances<\/p>\n<\/li>\n<\/ul>\n<hr \/>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">Mistake 3: Running Node.js Without a Process Manager<\/h3>\n<p class=\" text-lg my-6\"><strong>Problem:<\/strong> If your Node.js app crashes, your entire website goes down.<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-bash whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-comment\"># \u274c WRONG - No process manager<\/span>\nnode app.js\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>Solution:<\/strong> Use PM2 (Process Manager 2) to keep your Node.js app running:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-bash whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-comment\"># Install PM2 globally<\/span>\nnpm install -g pm2\n\n<span class=\"hljs-comment\"># Start your app with PM2<\/span>\npm2 start app.js --name <span class=\"hljs-string\">\"myapp\"<\/span>\n\n<span class=\"hljs-comment\"># Make PM2 start on system reboot<\/span>\npm2 startup\npm2 save\n\n<span class=\"hljs-comment\"># Monitor your app<\/span>\npm2 status\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>What PM2 does:<\/strong><\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Automatically restarts your app if it crashes<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Manages multiple instances of your app for load balancing<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Provides monitoring and logs<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\">Starts your app automatically when the server reboots<\/p>\n<\/li>\n<\/ul>\n<p class=\" text-lg my-6\"><strong>Complete architecture:<\/strong><\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-title class_\">Internet<\/span>\n   \u2193\n[<span class=\"hljs-title class_\">Caddy<\/span> - <span class=\"hljs-title class_\">Port<\/span> <span class=\"hljs-number\">443<\/span>\/<span class=\"hljs-number\">80<\/span>]\n   \u2193 (reverse proxy)\n[<span class=\"hljs-title class_\">PM2<\/span> managing <span class=\"hljs-title class_\">Node<\/span>.<span class=\"hljs-property\">js<\/span> instances]\n   \u251c\u2500\u2500 <span class=\"hljs-title class_\">Instance<\/span> <span class=\"hljs-number\">1<\/span> (<span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3000<\/span>)\n   \u251c\u2500\u2500 <span class=\"hljs-title class_\">Instance<\/span> <span class=\"hljs-number\">2<\/span> (<span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3001<\/span>)\n   \u2514\u2500\u2500 <span class=\"hljs-title class_\">Instance<\/span> <span class=\"hljs-number\">3<\/span> (<span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3002<\/span>)\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<hr \/>\n<h2 class=\"text-3xl font-semibold mt-14 mb-8 \">Testing Your Security Headers<\/h2>\n<p class=\" text-lg my-6\">After applying the headers, verify they&#8217;re actually being sent:<\/p>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">Using curl (Command Line)<\/h3>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-bash whitespace-pre-wrap break-words text-gray-300\">curl -I https:\/\/example.com\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\">Output should show your headers:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"><span class=\"hljs-variable constant_\">HTTP<\/span>\/<span class=\"hljs-number\">2<\/span> <span class=\"hljs-number\">200<\/span>\n...\n<span class=\"hljs-title class_\">Strict<\/span>-<span class=\"hljs-title class_\">Transport<\/span>-<span class=\"hljs-title class_\">Security<\/span>: max-age=<span class=\"hljs-number\">31536000<\/span>; includeSubDomains; preload\nX-<span class=\"hljs-title class_\">Content<\/span>-<span class=\"hljs-title class_\">Type<\/span>-<span class=\"hljs-title class_\">Options<\/span>: nosniff\nX-<span class=\"hljs-title class_\">Frame<\/span>-<span class=\"hljs-title class_\">Options<\/span>: <span class=\"hljs-variable constant_\">DENY<\/span>\nX-<span class=\"hljs-variable constant_\">XSS<\/span>-<span class=\"hljs-title class_\">Protection<\/span>: <span class=\"hljs-number\">0<\/span>\n<span class=\"hljs-title class_\">Referrer<\/span>-<span class=\"hljs-title class_\">Policy<\/span>: strict-origin-when-cross-origin\n<span class=\"hljs-title class_\">Permissions<\/span>-<span class=\"hljs-title class_\">Policy<\/span>: geolocation=(), microphone=(), camera=()\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<h3 class=\"text-2xl mt-10 mb-4 font-bold \">Using Online Tools<\/h3>\n<p class=\" text-lg my-6\">Visit these websites and enter your domain:<\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><a class=\"! !underline\" href=\"https:\/\/securityheaders.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">securityheaders.com<\/a> &#8211; Analyzes your headers and gives a grade<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><a class=\"! !underline\" href=\"https:\/\/observatory.mozilla.org\/\" target=\"_blank\" rel=\"noopener noreferrer\">Mozilla Observatory<\/a> &#8211; Comprehensive security scan<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><a class=\"! !underline\" href=\"https:\/\/www.qualysssl.com\/ssl-scan\/\" target=\"_blank\" rel=\"noopener noreferrer\">qualysssl.com<\/a> &#8211; SSL\/TLS certificate validation<\/p>\n<\/li>\n<\/ul>\n<p class=\" text-lg my-6\">These tools show you exactly which headers you&#8217;re missing and which ones need improvement.<\/p>\n<hr \/>\n<h2 class=\"text-3xl font-semibold mt-14 mb-8 \">Advanced: Adding Content-Security-Policy (CSP)<\/h2>\n<p class=\" text-lg my-6\">For stronger XSS protection, add a Content-Security-Policy header:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\">example.<span class=\"hljs-property\">com<\/span> {\n    header {\n        <span class=\"hljs-title class_\">Strict<\/span>-<span class=\"hljs-title class_\">Transport<\/span>-<span class=\"hljs-title class_\">Security<\/span> <span class=\"hljs-string\">\"max-age=31536000; includeSubDomains; preload\"<\/span>\n        X-<span class=\"hljs-title class_\">Content<\/span>-<span class=\"hljs-title class_\">Type<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"nosniff\"<\/span>\n        X-<span class=\"hljs-title class_\">Frame<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"DENY\"<\/span>\n        X-<span class=\"hljs-variable constant_\">XSS<\/span>-<span class=\"hljs-title class_\">Protection<\/span> <span class=\"hljs-string\">\"0\"<\/span>\n        <span class=\"hljs-title class_\">Referrer<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"strict-origin-when-cross-origin\"<\/span>\n        <span class=\"hljs-title class_\">Permissions<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"geolocation=(), microphone=(), camera=()\"<\/span>\n        <span class=\"hljs-title class_\">Content<\/span>-<span class=\"hljs-title class_\">Security<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"default-src 'self'; script-src 'self' https:\/\/cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:\"<\/span>\n    }\n\n    reverse_proxy <span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3000<\/span>\n}\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<p class=\" text-lg my-6\"><strong>What CSP does:<\/strong><\/p>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">default-src 'self'<\/code>: Load all resources from your own domain by default<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">script-src 'self' https:\/\/cdn.example.com<\/code>: Allow scripts only from your domain and a trusted CDN<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">style-src 'self' 'unsafe-inline'<\/code>: Allow styles from your domain and inline styles<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><code class=\"break-words rounded bg-[#24292E] px-2 py-1 text-[#EEEEEE]\">img-src 'self' data: https:<\/code>: Allow images from your domain, data URLs, and HTTPS sources<\/p>\n<\/li>\n<\/ul>\n<p class=\" text-lg my-6\">This prevents any JavaScript from external sources from running on your page unless you explicitly allow it.<\/p>\n<hr \/>\n<h2 class=\"text-3xl font-semibold mt-14 mb-8 \">Complete Production-Ready Caddyfile Example<\/h2>\n<p class=\" text-lg my-6\">Here&#8217;s a complete example with best practices:<\/p>\n<div class=\"relative group\">\n<pre class=\"relative bg-[#1a1a1a] border border-gray-700 rounded-lg overflow-x-auto my-8 p-6\"><code class=\"hljs language-typescript whitespace-pre-wrap break-words text-gray-300\"># <span class=\"hljs-title class_\">Define<\/span> reusable security <span class=\"hljs-title function_\">headers<\/span>\n(security_headers) {\n    header {\n        <span class=\"hljs-title class_\">Strict<\/span>-<span class=\"hljs-title class_\">Transport<\/span>-<span class=\"hljs-title class_\">Security<\/span> <span class=\"hljs-string\">\"max-age=31536000; includeSubDomains; preload\"<\/span>\n        X-<span class=\"hljs-title class_\">Content<\/span>-<span class=\"hljs-title class_\">Type<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"nosniff\"<\/span>\n        X-<span class=\"hljs-title class_\">Frame<\/span>-<span class=\"hljs-title class_\">Options<\/span> <span class=\"hljs-string\">\"DENY\"<\/span>\n        X-<span class=\"hljs-variable constant_\">XSS<\/span>-<span class=\"hljs-title class_\">Protection<\/span> <span class=\"hljs-string\">\"0\"<\/span>\n        <span class=\"hljs-title class_\">Referrer<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"strict-origin-when-cross-origin\"<\/span>\n        <span class=\"hljs-title class_\">Permissions<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"geolocation=(), microphone=(), camera=()\"<\/span>\n        <span class=\"hljs-title class_\">Content<\/span>-<span class=\"hljs-title class_\">Security<\/span>-<span class=\"hljs-title class_\">Policy<\/span> <span class=\"hljs-string\">\"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:\"<\/span>\n    }\n}\n\n# <span class=\"hljs-title class_\">Main<\/span> application\nexample.<span class=\"hljs-property\">com<\/span> {\n    <span class=\"hljs-keyword\">import<\/span> security_headers\n    \n    # <span class=\"hljs-title class_\">Enable<\/span> <span class=\"hljs-variable constant_\">GZIP<\/span> compression\n    encode gzip\n    \n    # <span class=\"hljs-title class_\">Reverse<\/span> proxy to <span class=\"hljs-title class_\">Node<\/span>.<span class=\"hljs-property\">js<\/span> app\n    reverse_proxy <span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3000<\/span>\n}\n\n# <span class=\"hljs-variable constant_\">API<\/span> subdomain\napi.<span class=\"hljs-property\">example<\/span>.<span class=\"hljs-property\">com<\/span> {\n    <span class=\"hljs-keyword\">import<\/span> security_headers\n    \n    reverse_proxy <span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3001<\/span>\n}\n\n# <span class=\"hljs-title class_\">Blog<\/span> subdomain\nblog.<span class=\"hljs-property\">example<\/span>.<span class=\"hljs-property\">com<\/span> {\n    <span class=\"hljs-keyword\">import<\/span> security_headers\n    \n    reverse_proxy <span class=\"hljs-attr\">localhost<\/span>:<span class=\"hljs-number\">3002<\/span>\n}\n<\/code><\/pre>\n<p><button class=\"absolute top-4 cursor-pointer right-4 p-2 rounded-md bg-[#24292e] hover:bg-gray-700 border border-gray-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200\" title=\"Copy code\"><\/button><\/div>\n<hr \/>\n<h2 class=\"text-3xl font-semibold mt-14 mb-8 \">Key Takeaways<\/h2>\n<ol class=\"list-decimal ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>Security headers are critical:<\/strong> They provide an essential layer of protection against common web attacks.<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>Caddy makes it simple:<\/strong> Unlike other servers, Caddy makes adding security headers straightforward and centralized.<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>Use reusable snippets:<\/strong> Don&#8217;t repeat yourself. Define security headers once and import them everywhere.<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>Always proxy through Caddy:<\/strong> Never expose Node.js directly to the internet. Always use a reverse proxy like Caddy.<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>Use a process manager:<\/strong> Keep your Node.js app running reliably with PM2.<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>Test your headers:<\/strong> Use securityheaders.com or Mozilla Observatory to verify everything is working.<\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><strong>This is production-ready:<\/strong> The configuration in this guide is suitable for production websites serving real users.<\/p>\n<\/li>\n<\/ol>\n<hr \/>\n<h2 class=\"text-3xl font-semibold mt-14 mb-8 \">Further Reading<\/h2>\n<ul class=\"list-disc ml-6 my-6\">\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><a class=\"! !underline\" href=\"https:\/\/caddyserver.com\/docs\/caddyfile\/directives\/header\" target=\"_blank\" rel=\"noopener noreferrer\">Caddy Documentation &#8211; Headers<\/a><\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><a class=\"! !underline\" href=\"https:\/\/owasp.org\/www-project-secure-headers\/\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP Security Headers Guide<\/a><\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><a class=\"! !underline\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Headers\" target=\"_blank\" rel=\"noopener noreferrer\">Mozilla Developer Network &#8211; HTTP Headers<\/a><\/p>\n<\/li>\n<li class=\" text-lg my-2\">\n<p class=\" text-lg my-6\"><a class=\"! !underline\" href=\"https:\/\/pm2.keymetrics.io\/\" target=\"_blank\" rel=\"noopener noreferrer\">PM2 Documentation<\/a><\/p>\n<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Introduction: Why Security Headers Matter When you build a web application and deploy it on the internet, you&#8217;re exposed to various attacks. Hackers use different techniques to steal data, hijack sessions, or redirect users to malicious sites. While firewalls and HTTPS provide basic protection, security headers are an additional layer of defense that run at&#8230;<\/p>\n","protected":false},"author":1,"featured_media":4739,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[212,236],"tags":[220],"class_list":["post-4906","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops-infrastructure","category-digital-transformation","tag-web","th-blog blog-single has-post-thumbnail"],"_links":{"self":[{"href":"https:\/\/www.softcolon.com\/blogs\/wp-json\/wp\/v2\/posts\/4906","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.softcolon.com\/blogs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.softcolon.com\/blogs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.softcolon.com\/blogs\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.softcolon.com\/blogs\/wp-json\/wp\/v2\/comments?post=4906"}],"version-history":[{"count":2,"href":"https:\/\/www.softcolon.com\/blogs\/wp-json\/wp\/v2\/posts\/4906\/revisions"}],"predecessor-version":[{"id":4908,"href":"https:\/\/www.softcolon.com\/blogs\/wp-json\/wp\/v2\/posts\/4906\/revisions\/4908"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.softcolon.com\/blogs\/wp-json\/wp\/v2\/media\/4739"}],"wp:attachment":[{"href":"https:\/\/www.softcolon.com\/blogs\/wp-json\/wp\/v2\/media?parent=4906"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.softcolon.com\/blogs\/wp-json\/wp\/v2\/categories?post=4906"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.softcolon.com\/blogs\/wp-json\/wp\/v2\/tags?post=4906"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}