Web Security Essentials: Protecting Your Business Online
43% of cyberattacks target small businesses. HTTPS, security headers, input validation, and OWASP best practices aren't optional — they're the minimum standard for any business website in 2026.
Small business owners often assume cybercriminals aren't interested in them. They're wrong. 43% of cyberattacks target small businesses, and the average cost of a data breach for a small business is $200,000 — enough to close most businesses permanently. Website security isn't a technical nicety. It's business survival infrastructure.
The Threat Landscape in 2026
Attacks on business websites in 2026 are largely automated. Botnets continuously scan the internet for known vulnerabilities, unpatched software, and weak configurations. When they find one, they exploit it — automatically, at scale, without a human making a specific decision to target your business.
The most common attack types against business websites:
- SQL injection: Inserting malicious database commands through input fields
- Cross-site scripting (XSS): Injecting malicious JavaScript into pages viewed by other users
- Credential stuffing: Using leaked username/password combinations from other breaches to access admin accounts
- DDoS: Flooding your server with traffic until it goes offline
- Supply chain attacks: Compromising third-party JavaScript libraries your site loads
- WordPress plugin vulnerabilities: The most common attack vector for small business websites
Layer 1: HTTPS and Transport Security
HTTPS is the absolute baseline. If your site is still running on HTTP in 2026, stop reading and fix that first. Every browser flags HTTP sites as "Not Secure." Google has used HTTPS as a ranking signal since 2014. There is no legitimate reason not to have HTTPS.
Setting Up SSL/TLS Correctly
Getting a certificate is free via Let's Encrypt. But configuration matters:
# nginx — secure TLS configuration
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Disable old, insecure protocols
ssl_protocols TLSv1.2 TLSv1.3;
# Strong cipher suites only
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS — force HTTPS for 1 year, including subdomains
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
HSTS — HTTP Strict Transport Security
HSTS tells browsers to always use HTTPS for your domain, even if a user types http://. Once a browser has seen your HSTS header, it will never make an unencrypted request to your domain. Submit your domain to the HSTS preload list (hstspreload.org) to get this protection for first-time visitors too.
Layer 2: HTTP Security Headers
Security headers are HTTP response headers that instruct browsers how to behave when loading your pages. They're one of the highest-ROI security investments you can make — a single nginx configuration block provides significant protection against entire categories of attacks.
Content Security Policy (CSP)
CSP is the most powerful security header and the most complex to implement. It tells browsers exactly which sources of scripts, styles, images, and other resources are allowed to load on your pages. A properly configured CSP makes XSS attacks extremely difficult — the injected script simply won't execute if it's not from an approved source.
# nginx — CSP header
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'nonce-{RANDOM_NONCE}' https://www.googletagmanager.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https://www.google-analytics.com;
connect-src 'self' https://www.google-analytics.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
" always;
Start in report-only mode to identify what you'd break before enforcing:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-violations
Other Essential Headers
# nginx — complete security header block
# Prevent MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;
# Prevent clickjacking
add_header X-Frame-Options "DENY" always;
# Control referrer information
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Control browser features
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(self), payment=()" always;
# Remove server version from responses
server_tokens off;
Test your headers at securityheaders.com. Aim for an A+ rating.
Layer 3: Input Validation and SQL Injection Prevention
SQL injection remains the #1 attack technique against web applications despite being fully preventable. It happens when user input is inserted directly into database queries:
// VULNERABLE — never do this
const userId = req.query.id; // Could be: "1; DROP TABLE users; --"
const query = `SELECT * FROM users WHERE id = ${userId}`;
await db.query(query);
// SAFE — use parameterized queries
const userId = req.query.id;
const query = 'SELECT * FROM users WHERE id = $1';
await db.query(query, [userId]);
The rule is simple: never trust user input. Validate, sanitize, and parameterize everything. This applies to:
- URL parameters
- Form fields
- HTTP headers (yes, headers can be forged)
- Cookies
- File uploads
- API request bodies
Input Validation with Zod
In a Node.js/TypeScript stack, Zod provides runtime type validation that's both developer-friendly and security-conscious:
import { z } from 'zod';
const ContactSchema = z.object({
name: z.string().min(1).max(100).trim(),
email: z.string().email().max(254),
phone: z.string().regex(/^+?[ds-()]{7,20}$/).optional(),
message: z.string().min(10).max(2000).trim(),
});
// In your API route
export async function POST(request: Request) {
const body = await request.json();
const result = ContactSchema.safeParse(body);
if (!result.success) {
return Response.json(
{ error: 'Invalid input', details: result.error.flatten() },
{ status: 400 }
);
}
// result.data is now typed and validated
await sendEmail(result.data);
return Response.json({ success: true });
}
Layer 4: Authentication Security
Weak authentication is responsible for 80% of hacking-related breaches. If your site has user accounts or an admin panel, these are non-negotiable:
Password Requirements and Hashing
Never store passwords in plain text or with reversible encryption. Use bcrypt, Argon2, or scrypt:
import bcrypt from 'bcryptjs';
// Hashing (at registration)
const saltRounds = 12; // Higher = slower = more secure
const hashedPassword = await bcrypt.hash(plainPassword, saltRounds);
await db.saveUser({ email, password: hashedPassword });
// Verification (at login)
const isValid = await bcrypt.compare(plainPassword, storedHash);
if (!isValid) {
// Use the same error message for wrong email OR wrong password
// This prevents user enumeration attacks
return Response.json({ error: 'Invalid credentials' }, { status: 401 });
}
Rate Limiting on Auth Endpoints
Without rate limiting, an attacker can try thousands of passwords per second against your login endpoint:
import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Maximum 5 failed attempts
skipSuccessfulRequests: true, // Don't count successful logins
message: { error: 'Too many login attempts. Try again in 15 minutes.' },
});
Multi-Factor Authentication
For admin panels and high-value accounts, MFA is mandatory. TOTP (Time-based One-Time Password) via apps like Google Authenticator or Authy is straightforward to implement and provides significant protection against credential theft.
Layer 5: Dependency Security
Your application's security is only as strong as your dependencies. A vulnerability in a package you haven't updated can compromise your entire site — even if your own code is perfect.
# Run weekly — check for known vulnerabilities
npm audit
# Fix automatically where possible
npm audit fix
# For remaining issues, review and update manually
npm update package-name
Set up automated dependency scanning:
- GitHub Dependabot: Automatically creates PRs for security updates
- Snyk: Deeper vulnerability scanning with fix guidance
- npm audit in CI: Block deployments when critical vulnerabilities are detected
Layer 6: The OWASP Top 10
The Open Web Application Security Project maintains the OWASP Top 10 — a list of the most critical web application security risks. In 2026, these are:
- Broken Access Control: Users accessing resources they shouldn't have permission to view
- Cryptographic Failures: Exposing sensitive data due to weak encryption or missing encryption
- Injection: SQL, NoSQL, command injection through unvalidated input
- Insecure Design: Security flaws in the architecture, not just the implementation
- Security Misconfiguration: Default credentials, unnecessary features enabled, verbose error messages
- Vulnerable Components: Using libraries with known vulnerabilities
- Authentication Failures: Weak passwords, missing MFA, session management issues
- Software and Data Integrity Failures: CI/CD pipeline vulnerabilities, auto-update without verification
- Logging and Monitoring Failures: Not detecting or responding to breaches
- Server-Side Request Forgery (SSRF): Tricking your server into making requests to internal resources
Review your site against each item. The OWASP website provides detailed testing guides for each category.
Layer 7: Monitoring and Incident Response
Security without monitoring is security theater. You need to know when something goes wrong:
What to Monitor
- Failed login attempts (rate and source IP)
- Admin page access from new IP addresses
- Unusual database query patterns
- File system changes (especially in web-accessible directories)
- Outbound connections to unfamiliar destinations
- SSL certificate expiry (failing to renew causes downtime and security warnings)
Set Up Alerts
// Example: Alert on too many 401s from same IP
const authFailures = new Map();
export function trackAuthFailure(ip: string) {
const count = (authFailures.get(ip) || 0) + 1;
authFailures.set(ip, count);
if (count === 5) {
// Alert your team
sendSecurityAlert({
type: 'brute_force_attempt',
ip,
timestamp: new Date().toISOString(),
});
}
}
The Security Checklist
Before launching any business website, verify:
- HTTPS with valid TLS 1.2+ certificate
- HSTS header with minimum 1-year max-age
- Content Security Policy configured and tested
- X-Content-Type-Options, X-Frame-Options headers present
- All user input validated server-side before processing
- Parameterized queries for all database operations
- Passwords hashed with bcrypt/Argon2 (never MD5, SHA1)
- Rate limiting on auth endpoints
- npm audit returning zero critical/high vulnerabilities
- Error messages don't expose stack traces or system info
- Admin panel at non-default URL (not /admin or /wp-admin)
- Server software versions hidden from response headers
- File upload endpoints restrict type, size, and destination
- Automated backups running daily with tested restoration
- Monitoring alerts configured for suspicious activity
Security is not a one-time project. It's an ongoing discipline. The landscape changes, new vulnerabilities are discovered, and your attack surface changes as you add features. Budget time for monthly security reviews, keep dependencies updated, and treat every new feature as a potential attack vector until proven otherwise.
The cost of prevention is always lower than the cost of a breach. Always.