09.05 Securing the Web Server
SimpleRisk runs behind a web server (Apache or nginx); the web server is the front door. Harden it with TLS 1.2+ only, strong cipher suites, security headers (HSTS, CSP, X-Content-Type-Options, X-Frame-Options, Referrer-Policy), rate limiting, and a WAF if your environment warrants. The defaults from a fresh install are usually too permissive for production.
Why this matters
The web server is the public-facing edge of every SimpleRisk install. Every authenticated session, every API call, every form submission goes through it before reaching PHP. A misconfigured web server exposes the application to attacks that the application code can't prevent: TLS downgrade attacks if old protocols are still enabled, cookie hijacking via missing security headers, clickjacking via missing X-Frame-Options, brute-force attacks via missing rate limiting.
SimpleRisk doesn't terminate TLS itself; the web server in front of it does. SimpleRisk doesn't send most security headers itself; the web server adds them. SimpleRisk doesn't enforce rate limits; if rate limiting is needed, the web server provides it. The web server's configuration is therefore the security boundary for everything that reaches the application.
The honest scope to know up front: most of what's in this article is web server configuration, not SimpleRisk configuration. SimpleRisk runs on Apache or nginx; you configure those tools. This article focuses on the configurations relevant to a SimpleRisk deployment; for general web server hardening, the official documentation for your web server is the authoritative source.
Before you start
Have these in hand:
- Root or sudo access to the SimpleRisk server (or to the web server if it's separate, behind a load balancer).
- The web server you're running — Apache, nginx, or other. Examples in this article cover Apache and nginx; adapt for others.
- A valid TLS certificate for your SimpleRisk hostname. Either commercial (purchased) or free via Let's Encrypt — both work; commercial is appropriate for installs with strict procurement requirements; Let's Encrypt is operationally simpler.
- A maintenance window if you're changing TLS configuration in production; users may need to re-authenticate.
- A test client (browser, curl) to verify configuration changes from outside the server.
Step-by-step
1. Use TLS 1.2 or TLS 1.3 only
Old TLS versions (TLS 1.0, TLS 1.1) and SSL (any version) have known vulnerabilities. Disable everything below TLS 1.2.
nginx:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_ecdh_curve secp384r1;
Apache (in your virtual host or ssl.conf):
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLHonorCipherOrder on
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
These configurations are based on Mozilla's "intermediate" recommendation (https://ssl-config.mozilla.org/), which balances modern security with broad client compatibility. If you can constrain to "modern" (TLS 1.3 only, no fallback), even better — but verify your client compatibility first.
Reload the web server after changes; test from outside:
# Test with openssl
openssl s_client -connect your-simplerisk.example.com:443 -tls1_2
# Or use online testers like ssllabs.com
Aim for an A or A+ rating on SSL Labs (https://www.ssllabs.com/ssltest/).
2. Enable HSTS
HSTS (HTTP Strict Transport Security) tells browsers to always use HTTPS, preventing TLS-stripping attacks.
nginx:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
Apache:
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
max-age=63072000 is two years (in seconds). includeSubDomains extends to subdomains. preload makes the entry eligible for the browser-preloaded HSTS list (https://hstspreload.org/) — but only enable preload if you're sure every subdomain is HTTPS-ready and you commit to keeping it that way.
3. Enable security headers
A baseline set of HTTP security headers:
nginx:
# Don't let MIME-type sniffing override declared types
add_header X-Content-Type-Options "nosniff" always;
# Don't let the page be framed (clickjacking protection)
add_header X-Frame-Options "SAMEORIGIN" always;
# Send a Referer only to same-origin requests
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Enable XSS protection (legacy header; modern browsers use CSP)
add_header X-XSS-Protection "1; mode=block" always;
# Permissions-Policy (formerly Feature-Policy)
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
Apache:
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-XSS-Protection "1; mode=block"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
Verify with curl -I https://your-simplerisk.example.com/ — the headers should appear in the response.
4. Configure Content Security Policy (CSP)
CSP is the strongest defense against XSS. SimpleRisk's UI uses inline scripts and styles in places, so a strict CSP requires careful tuning to avoid breaking the application.
A starting point that works with SimpleRisk's pattern (test thoroughly before committing):
nginx:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'; form-action 'self';" always;
'unsafe-inline' and 'unsafe-eval' weaken CSP's XSS protection but are needed for the current application. Tightening them requires application-side changes (using nonces or hashes); if your program needs strict CSP, file a feature request.
For starting out, CSP-Report-Only mode lets you see what would break without actually breaking it:
add_header Content-Security-Policy-Report-Only "default-src 'self'; ...; report-uri /csp-report" always;
The browser sends violation reports without blocking. Review the reports for a week, tune the policy, then switch to enforcing mode.
5. Configure cookie security
The session cookie should have the security flags. SimpleRisk sets HttpOnly by default; verify and add Secure and SameSite.
In simplerisk/includes/config.php (or PHP's session configuration):
ini_set('session.cookie_secure', '1'); // Only over HTTPS
ini_set('session.cookie_httponly', '1'); // Not accessible to JavaScript
ini_set('session.cookie_samesite', 'Lax'); // CSRF protection (or 'Strict' for tighter)
For SimpleRisk installs only accessed over HTTPS (which is what production deployments should be), session.cookie_secure should always be on.
6. Configure rate limiting
SimpleRisk has no native rate limiting. Web-server-level rate limiting protects against:
- Brute-force login attempts.
- API abuse from misbehaving integrations.
- Scraping of public-facing pages.
nginx:
http {
# Define rate-limit zones
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
# Rate-limit login attempts
location ~* ^(/index\.php|/login)$ {
limit_req zone=login_limit burst=5 nodelay;
try_files $uri $uri/ /index.php?$args;
}
# Rate-limit API
location /api/v2/ {
limit_req zone=api_limit burst=20 nodelay;
try_files $uri /api/v2/index.php?$args;
}
}
}
Apache (with mod_evasive or mod_qos):
mod_evasive:
DOSHashTableSize 2048 DOSPageCount 5 DOSSiteCount 100 DOSPageInterval 1 DOSSiteInterval 1 DOSBlockingPeriod 60
Tune to match your legitimate traffic patterns. Too tight and legitimate users get blocked; too loose and brute-force attempts succeed.
7. Restrict access to administrative paths (optional)
Some programs restrict access to administrative paths (/admin/, /configure/) to specific source IPs:
nginx:
location /admin/ {
allow 10.0.0.0/8; # Internal network
allow 192.168.1.100; # Specific admin workstation
deny all;
try_files $uri $uri/ /index.php?$args;
}
This is defense-in-depth — application-level admin permissions still apply, but the network layer prevents external access entirely. Appropriate for installs where admin access from outside specific networks is never legitimate.
8. Hide server software version
Default web server configurations expose the software version in the Server header. Hide it:
nginx:
server_tokens off;
Apache:
ServerTokens Prod
ServerSignature Off
Hiding the version doesn't substantively improve security (attackers fingerprint versions other ways), but it's a small step.
9. Disable unnecessary HTTP methods
SimpleRisk uses GET, POST, PUT, PATCH, DELETE. Methods like TRACE, OPTIONS (for non-CORS use), and CONNECT can be disabled if not needed.
nginx:
if ($request_method !~ ^(GET|POST|PUT|PATCH|DELETE|HEAD)$) {
return 405;
}
Apache:
Require all denied
10. Configure a Web Application Firewall (WAF) (recommended for production)
A WAF inspects HTTP traffic and blocks known attack patterns. Options:
- ModSecurity with the OWASP Core Rule Set (OWASP CRS) — free, open-source, runs as an Apache or nginx module.
- Cloudflare WAF — managed, easy to configure, requires Cloudflare proxying.
- AWS WAF — managed, integrates with AWS infrastructure.
- Imperva, F5, Akamai — enterprise WAFs with broader feature sets.
A WAF in front of SimpleRisk catches:
- SQL injection attempts (which Phan and SimpleRisk's PDO-based DB layer should already prevent, but defense in depth).
- Cross-site scripting attempts.
- Path traversal attempts.
- Known CVE exploitation patterns.
- High-volume scraping or scanning behavior.
Tune the WAF carefully — overly-aggressive rules block legitimate behavior. Start in observation mode, review false positives, then enforce.
Common pitfalls
A handful of patterns recur with web server hardening.
-
Allowing TLS 1.0 or TLS 1.1 for "compatibility." Both are deprecated and have known vulnerabilities. Modern browsers don't need them; if a client does, that client is the security problem.
-
Setting HSTS without testing first. Once browsers cache the HSTS header, they refuse HTTP for the entire
max-ageperiod. Test in non-production; start with a shortmax-age(e.g., 300 seconds) and increase as you gain confidence. -
Configuring strict CSP without testing. It will break the application in subtle ways. Use Report-Only mode first.
-
Skipping the
Secureflag on session cookies. Allows the cookie to transmit over HTTP, defeating HTTPS-only access patterns. -
Leaving rate limits at the default (none). Brute-force attempts against the login page are continuous on internet-exposed installs. Rate-limit them.
-
Restricting admin paths to "internal IPs" without verifying what "internal" means. A wide internal range may include guest WiFi, vendor-accessible networks, or other untrusted segments. Tight allowlists.
-
Configuring a WAF in enforcing mode without observation period. False positives during a release or workflow change cause user-impacting outages. Observe first, then enforce.
-
Hardening only the web server without the database and application. All three layers matter. See Securing the Database and the rest of this chapter.
-
Forgetting to test from outside the network. Internal testing might not catch issues that only appear from external clients (firewall rules, certificate chains, etc.).
-
Not monitoring web server logs. Logs without review produce no value. Forward to a SIEM or aggregator; alert on suspicious patterns (high 401 rate, scanning paths, etc.).
-
Trusting Cloudflare or your CDN as the only security layer. WAFs can be bypassed; legitimate-looking traffic still hits the origin. The web server should be hardened as if no WAF were in front.
-
Using self-signed certificates in production. Browsers reject them by default; users learn to click through the warning, which trains them to ignore certificate errors generally. Use a real certificate (Let's Encrypt is free).
-
Not redirecting HTTP to HTTPS. A user typing
http://your-simplerisk.example.comshould be redirected tohttps://. Configure the redirect in the web server.
Related
- The Encryption Extra Overview
- Enabling Encryption
- Key Management and Rotation
- Securing the Database
- HTTPS and TLS Configuration
- Rate Limiting and Quotas
- Session Management and Timeout
Reference
- Permission required: Root or sudo on the web server.
- Implementing files (nginx):
/etc/nginx/nginx.conf,/etc/nginx/sites-available/,/etc/nginx/conf.d/*.conf. - Implementing files (Apache):
/etc/apache2/apache2.conf,/etc/apache2/sites-available/,.conf /etc/apache2/mods-enabled/ssl.conf. - TLS configuration: TLS 1.2 and TLS 1.3 only; modern cipher suites (ECDHE preferred); strong DH parameters; valid certificate. Reference: Mozilla SSL Configuration Generator (https://ssl-config.mozilla.org/).
- Required security headers:
Strict-Transport-Security(HSTS);X-Content-Type-Options: nosniff;X-Frame-Options: SAMEORIGIN;Referrer-Policy;Content-Security-Policy(with care). - Cookie security:
session.cookie_secure = 1(HTTPS-only);session.cookie_httponly = 1(no JS access);session.cookie_samesite = 'Lax'or'Strict'(CSRF protection). - Rate limiting:
limit_req_zone(nginx);mod_evasiveormod_qos(Apache); WAF or CDN-level limits as additional layers. - WAF options: ModSecurity + OWASP Core Rule Set (free); Cloudflare WAF (managed); AWS WAF; commercial WAFs.
- External dependencies: A valid TLS certificate; tooling to test from outside the network (curl, openssl, ssllabs.com).