How to Harden and Penetration Test a Web Server

So you've been asked to "harden the web server and pen test it". Where do you start?

This guide is for general IT professionals who know their way around a server but don't do security work every day. It walks through a practical, repeatable process: pick a recognised hardening standard, decide which controls apply, check the application against OWASP, add layered protection, then test your work from the outside in.

No single tool makes a server secure. Security comes from layers, each catching what the one before it missed, and from checking regularly that those layers still hold.

A word of caution before you start: only scan or test systems you own or have written permission to test. Agree a testing window with the system owner, and take a backup first.

Step 1: Find a hardening guide and build your control list

Don't invent your own checklist. Start from a recognised standard written for your exact operating system and web server, then decide which of its controls apply to you.

Where to find good guides

Guide Best for Notes
CIS Benchmarks Linux distributions, Windows Server, nginx, Apache, IIS, databases, cloud accounts The most widely used starting point. Each control is graded Level 1 (sensible for most) or Level 2 (stricter, may break things).
DISA STIGs Government and high-assurance systems Very thorough and prescriptive. Useful as a second opinion on CIS.
Vendor baselines Your specific platform For example, the Microsoft Security Compliance Toolkit or Canonical's Ubuntu Security Guide, which can apply CIS profiles automatically.
ASD Essential Eight and ISM Australian organisations Patching, admin privileges, MFA and backups. Often expected by government and enterprise clients.
Mozilla SSL Configuration Generator TLS settings for your web server Generates a current, sensible cipher and protocol config.

Turn the guide into a control list

A benchmark can run to several hundred controls, and not all of them will suit your server. Work through it and record a decision for each one in a simple register:

Control Source Decision Reason or evidence
Disable SSH password login; keys only CIS Linux Implement Verified in sshd_config
Disable root login over SSH CIS Linux Implement Verified in sshd_config
Separate partition for /var/log CIS Linux Accept risk Cloud VM with log shipping; disk monitored
Hide server version banners CIS nginx Implement server_tokens off
Automatic security updates Essential Eight Implement unattended-upgrades enabled

The "Accept risk" rows matter as much as the others. They show you considered the control and made a deliberate choice, which is exactly what an auditor or client will want to see.

To check your work, run an automated audit. Lynis is free and quick on Linux, and CIS offers its CIS-CAT assessment tool. Re-run the audit after every major change.

Step 2: Check the application against OWASP

A perfectly hardened server can still host a vulnerable application. The OWASP (Open Worldwide Application Security Project) guides cover the application layer, and they map neatly onto your control list.

  • OWASP Top 10: the most common web application risks, such as broken access control, injection and security misconfiguration. Use it as a sanity check: can you say how each risk is handled?
  • Application Security Verification Standard (ASVS): a detailed list of verifiable requirements across three levels. Level 1 suits most public websites; Level 2 suits apps handling sensitive data.
  • Web Security Testing Guide (WSTG): explains how to test each area, which feeds directly into Step 7.
  • Secure Headers Project: recommended HTTP response headers, such as Content-Security-Policy, Strict-Transport-Security and X-Content-Type-Options.
  • Cheat Sheet Series: short, practical guidance on topics like session management, file uploads and password storage.

Add the relevant ASVS requirements to the same register from Step 1. One list covering the server and the application is far easier to maintain, and it gives you a single document to show stakeholders.

If you didn't write the application, involve its developers. Many OWASP items, such as input validation and access control, can only be fixed in the code.

Step 3: Put the right firewalls in front

The aim is simple: the public should reach only ports 80 and 443, and only through the filters you choose. Everything else is denied by default.

The basics, whatever your setup

  • Default deny, inbound and outbound. Open only what the server needs.
  • Lock down admin access. SSH, RDP and control panels should be reachable only from a VPN or a short list of admin IP addresses, never the whole internet.
  • Use a host firewall too. nftables or ufw on Linux, Windows Defender Firewall on Windows. If the network firewall is ever misconfigured, the host firewall still protects you.

Option A: Cloudflare in front of your server

Cloudflare's proxy hides your server's address and filters traffic through its WAF, rate limiting and bot protection. Two configuration steps are easy to miss, and both matter.

1. Block everyone except Cloudflare at the origin. Your server's real IP often leaks through old DNS records or email headers. If attackers can reach it directly, they bypass Cloudflare entirely. Allow ports 80 and 443 only from Cloudflare's published IP ranges, or go further with Authenticated Origin Pulls (mutual TLS) or a Cloudflare Tunnel, which needs no open inbound ports at all.

2. Restore the real visitor IP on the web server. Behind a proxy, every request appears to come from a Cloudflare address. Your logs, rate limits, fail2ban rules and the application's own security checks then see the wrong IP. At worst, fail2ban will start blocking Cloudflare itself. Cloudflare passes the original address in the CF-Connecting-IP header, and your web server must convert it back (Cloudflare's guide):

# nginx (ngx_http_realip_module)
set_real_ip_from 173.245.48.0/20;   # repeat for every Cloudflare range
real_ip_header CF-Connecting-IP;
# Apache 2.4 (a2enmod remoteip)
RemoteIPHeader CF-Connecting-IP
RemoteIPTrustedProxy 173.245.48.0/20   # repeat for every Cloudflare range
# In LogFormat, use %a instead of %h

Only trust that header from Cloudflare's ranges. Otherwise anyone can forge it and pretend to be any IP they like. Cloudflare's list changes occasionally, so script a regular update of both the firewall allowlist and these directives.

Only Cloudflare reaches the origin, and the web server restores the real IP dropped: not a Cloudflare IP Visitor Real IP address Cloudflare WAF, rate limits adds CF-Connecting-IP Origin firewall 80/443 allowed from Cloudflare IPs only Web server and app restores real IP from the trusted header Attacker targets the leaked origin IP directly
Genuine traffic passes through Cloudflare's filters; anything sent straight to the origin IP is dropped by the firewall.

Option B: pfSense on your own network

For on-premises or co-located servers, pfSense makes a capable perimeter firewall. Add the Suricata or Snort package to inspect traffic against signature rulesets (such as Emerging Threats Open), and pfBlockerNG to block known-bad IP addresses and unwanted countries.

One limitation: signatures can't see inside encrypted HTTPS traffic unless TLS ends before the firewall. Network signatures still catch scans, exploit attempts on other ports and known-bad hosts, but pair them with a WAF for application attacks.

Option C: Your cloud provider's built-in WAF

If you host on a major cloud, its native WAF is often the simplest choice:

  • AWS: AWS WAF on CloudFront, an Application Load Balancer or API Gateway, with the AWS Managed Rules core rule set. Combine it with tight security groups.
  • Azure: Azure Web Application Firewall on Application Gateway or Front Door, using the managed OWASP-based rulesets. Combine it with network security groups.

The same real-IP rule applies behind any load balancer or proxy: make sure the web server reads the client address from the trusted forwarding header.

Tip for any WAF: start in detection ("count") mode for a week or two, review what it would have blocked, tune out false positives, then switch to blocking. A WAF that breaks your checkout gets switched off, and then it protects nothing.

Step 4: Add protection on the server itself

Perimeter controls can be bypassed or misconfigured, so the server should be able to spot and stop trouble on its own.

Intrusion detection and prevention with Suricata

Suricata is a free, open-source engine that inspects network traffic against signature rules. On a server it runs in two modes:

  • IDS (detection): watches a copy of the traffic and raises alerts. Low risk, so start here.
  • IPS (prevention): sits inline (on Linux, via NFQUEUE with nftables) and drops matching traffic. Move to this once you've tuned out false positives.

Use the free Emerging Threats Open ruleset, update it daily with suricata-update, and send alerts somewhere a human will see them (see Step 5).

As with pfSense, Suricata sees encrypted HTTPS traffic before your web server decrypts it, so it can't read the requests themselves. For that, add a web application firewall module on the server, such as ModSecurity or Coraza with the OWASP Core Rule Set. Add fail2ban to block repeated login failures, and consider file integrity monitoring (AIDE, or Wazuh for a fuller host intrusion detection system) to alert you when key files change.

Is ClamAV good enough for antivirus?

ClamAV is free, open source and maintained by Cisco Talos. It's a good tool for specific jobs, but it isn't a complete endpoint protection product.

Where it works well:

  • Scanning files users upload, before your application stores or serves them. This is its best use on a web server.
  • Scheduled scans of web roots for known web shells and malicious scripts.
  • Scanning email attachments on mail servers.

Where it falls short:

  • It relies mostly on signatures, so it is weaker against new or targeted malware than commercial engines.
  • It offers no behavioural detection, so it won't notice an attacker using legitimate tools.
  • The scanning daemon (clamd) holds its signatures in memory and can use well over 1 GB of RAM, which matters on small servers.

Our view: use ClamAV for upload scanning and scheduled web root scans, and pair it with Linux Malware Detect, which targets threats common on web hosting. On Windows Server, Microsoft Defender is built in and should stay on. For servers holding sensitive data, budget for a commercial endpoint detection and response (EDR) product, which watches behaviour as well as signatures.

Step 5: Monitor, back up and watch for new vulnerabilities

Hardening is a point-in-time job. These three practices keep the server safe in the months that follow.

Monitoring

You need to know quickly when something is down, and when something looks wrong.

  • Uptime and resources: PRTG Network Monitor is well suited to checking availability, response times, CPU, memory, disk space and certificate expiry, with alerts by email, SMS or push.
  • Logs: Site24x7 can collect web server, system and application logs centrally and alert on patterns such as bursts of failed logins, spikes in 4xx or 5xx errors, or Suricata alerts.

Shipping logs off the server has a second benefit: an attacker who gets in can't quietly delete the evidence. Whatever tools you choose, make sure every alert reaches a named person who knows what to do with it.

Off-infrastructure backups

Ransomware and compromised admin accounts often target backups first. Follow the 3-2-1 rule: three copies, on two different types of storage, with one copy off-site.

  • Keep at least one copy outside your main infrastructure, in a separate cloud account or with a different provider, using credentials the web server doesn't hold.
  • Make that copy immutable where possible (for example, S3 Object Lock or Azure immutable blob storage), so it can't be changed or deleted for a set period.
  • Test restores on a schedule. A backup you've never restored is a hope, not a plan.

Vulnerability alerts with OpenCVE

New vulnerabilities are published every day, and you can't read them all. OpenCVE lets you subscribe to the vendors and products in your stack and alerts you when a relevant CVE is published or updated, by email, webhook or Slack (OpenCVE docs). You can use the hosted service or self-host it with Docker.

  1. List everything in your stack: operating system, web server, language runtime (such as PHP, Node.js or .NET), database, CMS, plugins and major libraries.
  2. Subscribe to each in OpenCVE.
  3. When an alert arrives, check whether your version is affected and whether the vulnerable feature is exposed, then patch according to severity.

Pair this with automatic OS security updates and a dependency scanner for application code, such as GitHub Dependabot or npm audit.

Step 6: Run the basic external checks

Before a full penetration test, check your server the way an attacker would first see it. These checks take minutes and catch a surprising number of problems.

Port scan with Zenmap (Nmap)

Zenmap is the graphical front end for Nmap. Run it from a machine outside your network, so you see what the internet sees. The "Intense scan, all TCP ports" profile is a good start, or on the command line:

nmap -Pn -sV -p- your-server.example.com

Check that:

  • Only the ports you intended are open, typically 80 and 443.
  • SSH, RDP, database and control panel ports are not reachable from the internet.
  • Service versions shown match what you expect and are patched.
  • If you use Cloudflare, scanning your origin IP directly shows no open web ports. If it does, your origin lockdown from Step 3 isn't working.

TLS check with SSL Labs

Qualys SSL Labs grades your HTTPS configuration from A+ to F. Aim for an A or A+, which generally means TLS 1.2 and 1.3 only, strong ciphers, a complete certificate chain and HSTS enabled. If you're behind Cloudflare or a cloud load balancer, the test grades that service's settings, so check the origin's TLS config separately.

Header check

SecurityHeaders.com and Mozilla's HTTP Observatory check your HTTP response headers against the OWASP recommendations from Step 2, and tell you exactly which ones are missing.

Step 7: Penetration test the application with ZAP

ZAP (formerly OWASP ZAP, now ZAP by Checkmarx) is a free, open-source web application scanner. It sits between your browser and the site, records every request, and can then attack the application to find weaknesses such as injection flaws, cross-site scripting and missing protections.

Before you scan

  • Test a staging copy if you can. An active scan submits every form it finds. It can create junk records, send emails, trigger password resets and lock accounts.
  • Decide where the WAF fits. Scanning through your WAF tests the WAF. Scanning the origin directly (with your IP temporarily allowed) tests the application itself. Ideally, do both.
  • Get written approval and tell anyone monitoring alerts, so your scan isn't mistaken for a real attack.

A practical scanning process

  1. Baseline scan (safe). This spiders the site and checks responses passively, without attacking anything. It's a good first look, even on production:

    docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py -t https://staging.example.com
  2. Explore manually. In ZAP Desktop, use Manual Explore to open a browser routed through ZAP. Log in, then click through every page and feature, so ZAP learns the whole application.

  3. Spider. Run the standard spider, then the AJAX spider for JavaScript-heavy sites, to find pages you missed.
  4. Set up authentication. Create a context and a test user in ZAP so it can scan pages behind the login. Most serious vulnerabilities live there.
  5. Active scan. Run the active scanner against the context. This is the actual attack phase and can take hours on a large site. The same container image also offers zap-full-scan.py for unattended runs.
  6. Review the alerts. ZAP rates each finding by risk and confidence. Verify each one by hand, discard false positives, and record genuine issues against your control register.
  7. Fix and re-test. Re-scan after fixes to confirm they worked.
  8. Automate it. ZAP's Automation Framework can run the same scans in your deployment pipeline, so new releases are checked automatically.

Know the limits of automated scanning

Scanners are good at finding technical flaws but poor at understanding how your business works. They usually won't spot:

  • One user viewing another user's records by changing an ID in the URL.
  • Skipping a payment step, or applying a discount twice.
  • Weak password reset or account recovery flows.

Test these by hand, using the OWASP Web Security Testing Guide as your checklist. For systems holding payment, health or personal data, a professional penetration test is worth the investment.

Your hardening checklist

  • Choose a hardening guide (such as CIS) for your OS and web server
  • Build a control register with a decision and evidence for every control
  • Add the relevant OWASP ASVS requirements to the same register
  • Expose only ports 80 and 443, and restrict admin access to a VPN or known IPs
  • Put a WAF in front: Cloudflare, pfSense with signatures, or your cloud provider's WAF
  • Behind a proxy, lock the origin to the proxy's IPs and restore real visitor IPs
  • Run Suricata, a server-side WAF module and fail2ban
  • Scan uploads and web roots with ClamAV, or use a commercial EDR for sensitive systems
  • Monitor uptime and logs, with alerts going to a named person
  • Keep immutable backups off your main infrastructure, and test restores
  • Subscribe to your stack in OpenCVE
  • Check ports with Zenmap, TLS with SSL Labs and headers with SecurityHeaders.com
  • Scan the application with ZAP, then test business logic by hand
  • Repeat the audit and scans after every major change, and at least quarterly

Need a hand?

Hardening and testing a server properly takes time, and the details are where most problems hide. At James Anthony Consulting, security auditing, hardening and penetration testing are part of what we do every day, for systems we built and systems we've inherited.

If you'd like a second pair of eyes on your setup, help working through your findings, or an independent security audit, we'd love to hear from you. Reach out to the JAC team to find out more.

Zachary Bailey

Zac is a tactical software architect and Managing Director at James Anthony Consulting (JAC), which he founded in 2014. With two decades of IT experience, he specialises in delivering custom software solutions to SMEs and driving effective team communication. Zac’s expertise spans project management, technical troubleshooting, and advanced domain knowledge in health and retail e-commerce. His leadership has propelled JAC’s growth, establishing it as a trusted provider in Adelaide and beyond.

Previous
Previous

Running a Software Firm That Creates Client Value

Next
Next

Code Security Reviews in 2026