
Security vulnerabilities are discovered in web applications constantly. Some are sophisticated attacks requiring deep technical knowledge to execute. Many are basic mistakes — missing input validation, outdated dependencies, misconfigured headers — that are straightforward to prevent.
This guide covers the most important security practices for web developers: the vulnerabilities you need to understand, the defences you need to implement, and how monitoring fits into your security posture.
Every web application should use HTTPS. This is no longer optional — browsers penalise non-HTTPS sites, Google uses HTTPS as a ranking signal, and users have come to expect the padlock.
What HTTPS provides:
Getting HTTPS right:
See complete guide to SSL certificates for implementation details and domain monitor for SSL certificate expiry monitoring.
Security headers are HTTP response headers that instruct browsers on security policies. They're one of the highest-leverage security improvements you can make — they take minutes to implement and address significant attack categories.
Tells browsers to always use HTTPS for your domain, preventing SSL stripping attacks:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age — How long browsers should enforce HTTPS (seconds). Start with a short value, increase once confirmed working.includeSubDomains — Applies to all subdomainspreload — Submit to the HSTS preload list so browsers use HTTPS even before the first visitDefines which sources are allowed to load content on your page. Prevents cross-site scripting (XSS) and data injection attacks:
Content-Security-Policy: default-src 'self'; script-src 'self' cdn.example.com; img-src 'self' data: https:
CSP is the most powerful but most complex security header. Start in report-only mode to identify issues before enforcing:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
Prevents your page from being embedded in iframes on other sites, blocking clickjacking attacks:
X-Frame-Options: DENY
Or if you need to allow framing from specific origins:
X-Frame-Options: SAMEORIGIN
Prevents browsers from MIME-type sniffing — interpreting a file differently from its declared content type:
X-Content-Type-Options: nosniff
Controls how much referrer information is sent when navigating away from your site:
Referrer-Policy: strict-origin-when-cross-origin
Controls which browser features and APIs the page can use:
Permissions-Policy: camera=(), microphone=(), geolocation=()
Use securityheaders.com to scan any URL and see which security headers are missing or misconfigured. It grades your headers and provides specific recommendations.
SQL injection occurs when user input is incorporated directly into database queries without sanitisation, allowing attackers to modify the query logic.
Vulnerable:
# DO NOT do this
query = f"SELECT * FROM users WHERE email = '{user_email}'"
An attacker can input '; DROP TABLE users; -- and execute arbitrary SQL.
Safe — use parameterised queries:
# Always use parameterised queries
cursor.execute("SELECT * FROM users WHERE email = %s", (user_email,))
ORMs (SQLAlchemy, Eloquent, ActiveRecord) use parameterised queries by default. Never construct SQL by string concatenation with user input.
XSS occurs when an attacker injects malicious JavaScript that executes in other users' browsers. It can steal session cookies, capture keystrokes, or redirect users.
Vulnerable:
<!-- Directly rendering user input -->
<div>{{ user.comment }}</div>
If user.comment contains <script>document.cookie</script>, that script executes.
Prevention:
innerHTML or dangerouslySetInnerHTML with user-controlled contenthttpOnly cookies so JavaScript can't access session tokensCSRF tricks authenticated users into making unwanted requests to your application. A malicious site can cause a logged-in user's browser to submit a form to your application.
Prevention:
SameSite=Strict or SameSite=Lax on session cookiesOrigin and Referer headers for state-changing requestsIDOR occurs when an application exposes internal object references (like database IDs) and doesn't verify that the requesting user has permission to access that object.
Vulnerable:
GET /api/invoices/12345
If the application returns the invoice for ID 12345 without checking that the logged-in user owns it, any authenticated user can access any invoice by guessing IDs.
Prevention:
Storing or transmitting sensitive data insecurely:
Common mistakes:
Prevention:
.env files for secrets and ensure they're in .gitignoreA broad category covering:
Prevention:
Authentication is a high-value attack target. Get it wrong and attackers bypass all your other security controls.
Always hash passwords with a modern adaptive algorithm:
from passlib.hash import argon2
# Hashing
hashed = argon2.hash(password)
# Verification
is_valid = argon2.verify(password, hashed)
Argon2 and bcrypt are the recommended choices. Never use MD5, SHA-1, or SHA-256 directly for passwords — these are fast hashing algorithms, making brute-force attacks easy.
Offer MFA. For sensitive applications, require it. Time-based one-time passwords (TOTP) via apps like Google Authenticator are the most common implementation.
httpOnly cookies to prevent JavaScript from accessing session tokensSecure flag to ensure cookies are only sent over HTTPSWithout rate limiting, brute-force attacks against login forms are trivial.
from flask_limiter import Limiter
limiter = Limiter(app)
@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute")
def login():
...
Your application's security includes all its dependencies. A vulnerability in a library you use is a vulnerability in your application.
Keep dependencies updated — Regularly update packages to get security patches. Outdated dependencies with known vulnerabilities are one of the most common attack vectors.
Use vulnerability scanning:
# Node.js
npm audit
# Python
pip-audit
# Ruby
bundle audit
Monitor for new vulnerabilities — GitHub's Dependabot automatically creates PRs for vulnerable dependencies. Enable it on your repositories.
Audit what you import — Understand what third-party packages you're using. Avoid packages with low maintenance, few users, or a history of security issues.
A WAF sits in front of your application and filters malicious requests before they reach your application server. It blocks common attack patterns: SQL injection attempts, XSS payloads, known scanner signatures.
Cloudflare, AWS WAF, and similar services offer WAF capabilities. For most sites, Cloudflare's free tier provides meaningful protection.
A DDoS attack floods your server with traffic, making it unavailable. CDN and WAF providers (Cloudflare) absorb most volumetric attacks at the network edge before they reach your server.
Run application processes with the minimum permissions necessary. Your web application shouldn't run as root. Database users should only have the permissions they need. File permissions should be as restrictive as possible.
Store secrets (database passwords, API keys, credentials) in environment variables or secret management services, never in source code.
# Check for secrets accidentally committed
git log --all --full-history -- '*.env'
Use tools like git-secrets or trufflehog to scan repositories for accidentally committed credentials.
Security monitoring is the detection layer — identifying attacks and breaches after prevention has been bypassed.
Downtime can be a security incident. A DDoS attack takes your site down. A defacement attack might return unexpected content. Sudden spikes in error rates can indicate an active attack.
Domain Monitor monitors your site continuously and alerts you the moment it goes down, returns unexpected errors, or shows response time anomalies. This gives you visibility into the availability layer of security. Create a free account to set up monitoring.
Log security-relevant events:
Log the who, what, and when for every security-relevant event, with enough detail to investigate an incident.
Set up automated vulnerability alerts for your dependencies. GitHub Dependabot and security advisory subscriptions ensure you're notified when a dependency you use has a known vulnerability.
A practical starting point for any web application:
Transport & Access
Headers
Input & Output
Authentication
Dependencies & Configuration
Monitoring
For SSL certificate monitoring and uptime monitoring, Domain Monitor covers both automatically. See uptime monitoring best practices for the monitoring side of your security posture.
Generative AI creates new content — text, images, code, and more. This guide explains how it works, what tools are available, and where it's genuinely useful versus overhyped.
Read moreCursor AI is an AI-powered code editor built on VS Code. Learn what it does, how it works, and whether it's the right tool for your development workflow.
Read moreClaude Opus is Anthropic's most capable AI model, built for complex reasoning and demanding tasks. Learn what it does, how it compares, and when to use it.
Read moreLooking to monitor your website and domains? Join our platform and start today.