Nobody is attacking you personally
That is the useful frame. Almost nothing that happens to a small site is targeted. It is automated, indiscriminate, and looking for a short list of known-easy things. Which means a short list of defences covers most of the risk, and the exotic material you read about is not where your effort belongs yet.
The machine
If you run your own virtual machine:
- SSH keys only.
PasswordAuthentication no,PermitRootLogin no. Then look at the logs a week after launch:journalctl -u ssh | grep -c "Failed password"on a fresh public box regularly runs to thousands per day. That is the ambient rate, arriving at every address on the internet. - A firewall with three ports open: 22, 80, 443. Everything else closed by default.
- Your database must not have a public IP. “It has a password” is not a plan. Shodan and similar services index open Postgres, Redis and MongoDB instances continuously, and unauthenticated Redis in particular has been mass-exploited for years. Bind to a private network, or to localhost, and reach it through the application or an SSH tunnel.
- Automatic security updates.
unattended-upgradeson Debian or Ubuntu. It is one package and it closes the window on the kernel and OpenSSL issues you will otherwise never patch.
The accounts
This is where the highest-value five minutes live, and it is not code.
Multi-factor authentication on: your domain registrar, your DNS provider, your email, your cloud account, your GitHub. The registrar and the email are the crown jewels: with control of your email, an attacker resets everything else; with control of your DNS, they receive your traffic and can issue a valid certificate for your domain.
Use a password manager, use unique passwords, and store the MFA recovery codes somewhere a second person can reach them. Locking yourself out is a more likely outcome than being attacked.
The dependencies, and an honest word about the noise
Turn on Dependabot or Renovate, both free. Run npm audit or your language’s equivalent in CI.
The honest part, which most advice omits: a large fraction of CVE alerts in a lockfile are for code paths you never execute: a vulnerability in the dev server of a build tool, or in a function your dependency does not call. If you treat every alert as urgent you will spend your week on it, and then you will start ignoring the feed, and then you will miss the one that matters.
So triage. Is this package in the running application, or only in the build? Is the vulnerable function reachable from your code? Is it remotely triggerable? Fix the reachable ones promptly, batch the rest monthly, and keep updating regularly so no single update is frightening.
The application, in four items
Parameterised queries, always. SQL injection is decades old and still near the top of every breach report, and it is still string concatenation. Your ORM does this for you until the moment somebody builds a WHERE clause with a template literal.
Authorisation checked per object, on the server. The most common real bug in small applications is not injection; it is /invoice/1234 where changing the number to 1235 shows somebody else’s invoice. Every handler that loads a record by ID must check that this session is allowed that record. Hiding the link in the UI is not a check.
Cookies set properly. HttpOnly so JavaScript cannot read the session, Secure so it is never sent over HTTP, SameSite=Lax so other sites cannot make authenticated requests on the user’s behalf.
A rate-limited login, from the previous module, plus a check of submitted passwords against known-breached lists. Credential stuffing is the highest-volume real attack on a small site.
Least privilege for the database user
The account your application connects with does not need to create tables, drop them, or read other databases. Give it SELECT, INSERT, UPDATE, DELETE on the tables it uses. Migrations run as a different, more powerful role, in a job you control.
This costs ten minutes and converts several categories of injection from catastrophe into incident.
Headers, and the one with teeth
Most security headers are one line each and worth adding. Content-Security-Policy is the one that actually prevents cross-site scripting, and the one people get wrong, because a strict policy breaks inline scripts and third-party embeds.
Deploy it in report-only mode first (Content-Security-Policy-Report-Only), collect violations for a week, then enforce. Going straight to enforcement on a live site breaks your own analytics tag on a Friday afternoon.
Free tools, all of them
ssh-audit for your SSH configuration, testssl.sh for your TLS, OWASP ZAP for a basic application scan, and Mozilla’s Observatory for headers. None of them cost anything, and running each once before launch is an afternoon that puts you ahead of most sites on the internet.
Every lesson here is open. Your progress is recorded once you pass the Agents and Automation exam.
Check your understanding
You have limited time before launch. Which of these gives the largest reduction in real risk for a small public site?
Pick the one you would defend. Your answer stays private.
Stuck on something?Ask Tri, your AI tutor
