Labs · Cyber Security
Lab: Host a Static Page on Nginx, Add Basic Security Headers and Verify Them with curl -I
Course: Cyber Security · Chapter 8: Hosting a Static Website and Changing the Configuration
Chapter 8 hosts a static website and changes the configuration; this lab adds security headers to it.
Chala mitrano! Security headers are small instructions that our server sends to the browser: "don't guess file types", "don't show me inside another site's frame", "load scripts only from me". Five lines of Nginx config, big protection. Aaj aapan te lavuya.
चला मित्रांनो! Security headers म्हणजे server browser ला पाठवतो त्या छोट्या सूचना: "file type चा अंदाज लावू नको", "मला दुसऱ्या site च्या frame मध्ये दाखवू नको", "scripts फक्त माझ्याकडून load कर". Nginx config च्या पाच lines, मोठं protection. आज आपण ते लावूया.
चलो दोस्तों! Security headers वो छोटे निर्देश हैं जो हमारा server browser को भेजता है: "file type का अंदाज़ा मत लगाओ", "मुझे किसी और site के frame में मत दिखाओ", "scripts सिर्फ मुझसे load करो"। Nginx config की पाँच lines, बड़ी सुरक्षा। आज हम वो लगाएँगे।
Suppose we are…
Suppose we are building the landing page for a BookMyShow college fest campaign on our own Nginx server. A security reviewer runs curl -I on the page and says: "No security headers. Someone could put your page inside an invisible frame on their site and trick users into clicking (clickjacking)." We fix it with a few add_header lines and prove it with curl.
Goal of this lab
By the end you will have:
- Your own
index.htmlserved by Nginx. - Five security headers added in one separate file:
X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Permissions-PolicyandContent-Security-Policy. - Proof from
curl -Iand the browser's DevTools.
What you need (all free)
- Your EC2 server with Nginx from Lab 7 (HTTP 80 open to My IP only).
- 30–35 minutes.
Safety and ethics
Test headers on your own server. Online header checkers are fine for your own site; do not use them to collect information about other companies' sites for attacks.
Steps
-
SSH to the server. Replace the default page with your own:
echo '<!doctype html><html><head><meta charset="utf-8"><title>Fest 2026</title></head><body><h1>College Fest 2026</h1><p>Hosted on my own Nginx.</p></body></html>' | sudo tee /usr/share/nginx/html/index.html -
Open
http://<your-server-ip>in your browser.What you should see: the heading College Fest 2026.
-
Check the headers before the change:
curl -I http://localhostWhat you should see:
Server,Date,Content-Type,Content-Length,Last-Modified,ETagand nothing about security. -
Create a separate file for the headers. Files in
/etc/nginx/conf.d/ending in.confare loaded inside thehttpblock:sudo nano /etc/nginx/conf.d/security-headers.conf -
Paste these lines, then save (Ctrl + O, Enter, Ctrl + X):
add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; add_header Content-Security-Policy "default-src 'self'" always;nosniffstops the browser from guessing file types,SAMEORIGINblocks other sites from framing your page,Referrer-Policylimits what address is shared when users click away,Permissions-Policyswitches off camera, mic and location, and the CSP (Content Security Policy) allows content only from your own site.alwaysadds them to error pages too. -
Test and reload:
sudo nginx -t && sudo systemctl reload nginx -
Check the headers again:
curl -I http://localhostWhat you should see: all five new headers in the response, for example
X-Frame-Options: SAMEORIGIN. -
Check an error page too:
curl -I http://localhost/nope.What you should see:
HTTP/1.1 404 Not Foundand the same five headers, thanks toalways. -
In your browser, press F12 → Network tab → reload the page → click the first request (
/or your IP) → Headers → Response Headers. Find the five headers. - Still in DevTools, open the Console tab and run
document.title. It works, because it is your own page; the CSP only blocks content from other sites.
Ravindra Bagale's Tip
Careful: if you write add_header inside a location block, Nginx forgets the headers from the http level for that location. Students then wonder why headers disappear on one page. Keep them in one place, or repeat all of them in that location. He trap khup common aahe!
Ravindra Bagale's Tip – मराठी
सावधान: location block मध्ये add_header लिहिलं, तर त्या location साठी Nginx http level चे headers विसरतो. मग students ला प्रश्न पडतो की एका page वर headers का गायब झाले. ते एकाच ठिकाणी ठेवा, किंवा त्या location मध्ये सगळे परत लिहा. हा trap खूप common आहे!
Ravindra Bagale's Tip – हिंदी
सावधान: अगर location block के अंदर add_header लिखा, तो उस location के लिए Nginx http level के headers भूल जाता है। फिर students सोचते हैं कि एक page पर headers गायब क्यों हो गए। उन्हें एक ही जगह रखो, या उस location में सब दोबारा लिखो। ये trap बहुत common है!
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
Forgetting always |
Headers missing on 404 and 500 pages | Add always at the end of each line |
add_header inside a location that has its own headers |
The http-level headers vanish for that location | Keep headers in one place or repeat all of them |
| CSP too strict for a real site with Google Fonts or analytics | Fonts or scripts stop loading | Add only the domains you need, for example font-src 'self' https://fonts.gstatic.com |
Adding Strict-Transport-Security on plain HTTP |
Browsers ignore it; can cause trouble later | Add HSTS only after HTTPS works (Lab 13) |
File saved as security-headers.txt |
Nginx ignores it | The name must end in .conf |
Self-check checklist
0 of 5 done
Try-at-home challenge
Create test-frame.html on your own laptop with <iframe src="http://<your-server-ip>/"></iframe> and open it in the browser. What happens, and which header caused it? Then look in the DevTools console for the message.
Check your answer
The frame stays empty or shows a "refused to connect" box. The Console says the page refused to be framed because X-Frame-Options is sameorigin. (The modern CSP way is frame-ancestors 'self'; note that default-src does not cover framing, so add frame-ancestors separately if you rely on CSP.) Your local file is a different origin, so framing is blocked: that is clickjacking protection working.
Clean up to avoid charges
Keep the server for Lab 9 if you are continuing today. Otherwise terminate it: EC2 → Instances → Instance state → Terminate (delete) instance.
Samjla ka? Five headers, one file, always, then curl -I to prove it. Aata pudhe jaauya: Chapter 9 adds PHP and a database.