Skip to content
Currently booked
← All posts
· 5 min read

Locking down a small website: Cloudflare, DNS, mail and the dev server

How this portfolio is secured, layer by layer: security headers, Cloudflare settings, DNSSEC, CAA, SPF, DKIM and DMARC, and a dev server with no public ports.

Request an AI summary

This website is static. There is no database, no login and no server code that could be broken into. That makes it safe by default, as far as the site itself goes. The parts that need attention are everything around it: the domain, the DNS records, the email that runs on the same domain, and the machine I deploy from.

Here is how each of those layers is set up, and what I’d recommend for any small website.

1. The site: headers that do the heavy lifting

The site is built with Astro and served by Cloudflare as static files. Every response carries a set of security headers, defined in one _headers file. The most important ones, shortened:

Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-…'; style-src 'self' 'sha256-…'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'
Strict-Transport-Security: max-age=63072000; includeSubDomains
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()

The important part is the Content Security Policy. Instead of the common shortcut 'unsafe-inline', a small build step hashes every inline script and style and writes those hashes into the policy. If someone ever manages to inject a script into a page, the browser refuses to run it. The same build step fails if a page contains inline style="" attributes or onclick="" handlers, so the policy can’t be weakened by accident.

2. Cloudflare: HTTPS only, modern TLS only

Two settings in Cloudflare complete what the headers start:

  • Always Use HTTPS redirects every http:// request to https://. HSTS alone doesn’t cover the very first visit over HTTP, because browsers ignore it there. The redirect closes that gap.
  • Minimum TLS version 1.2 refuses old and weak protocol versions.

Cloudflare’s own HSTS switch stays off, because the site already sends the header. Two sources for the same header only cause confusion.

3. Domain and DNS: DNSSEC, CAA and certificate alerts

DNSSEC signs the DNS answers, so nobody can slip a fake IP address to your visitors. Cloudflare does the signing once it’s switched on. The second half happens at the registrar, and for .de domains there is a catch: the registry expects the DNSKEY, not the DS record that Cloudflare shows first. In the registrar’s form that means unticking “automatic mode” (which only works with the registrar’s own nameservers) and pasting the key. A few minutes later the public resolvers validate the domain.

One rule to remember: before you ever move the domain to other nameservers, remove the key at the registrar first. Otherwise the domain stops resolving for everyone who checks DNSSEC.

CAA records list the certificate authorities that may issue certificates for the domain. Every other CA has to refuse. Cloudflare adds the CAs it uses on its own, so the list stays complete.

Certificate Transparency monitoring sends an email whenever any CA issues a certificate for the domain. If someone ever took over the domain, this is how I’d notice. The alert goes to an address outside the domain, because during a takeover the domain’s own mail may not arrive.

That’s also the real risk: not a hack of the registrar itself, but someone getting into the registrar account. So: two-factor authentication, a unique password, and a recovery email that doesn’t depend on the domain. DNSSEC doesn’t help against that, because whoever controls the account can change the key too.

4. Mail: SPF, DKIM and DMARC

Mail runs on the same domain, so it needs the usual three records:

  • SPF says which servers may send mail for the domain.
  • DKIM signs every outgoing mail, so receivers can tell it really comes from you.
  • DMARC tells receiving servers what to do with mail that fails these checks.

A common pitfall with Cloudflare: the DKIM record is usually a CNAME pointing to the mail provider. If it’s proxied (the orange cloud), Cloudflare answers with its own IP addresses instead of passing on the provider’s key, and every mail goes out unsigned. So: mail records never get the orange cloud, always DNS only.

To check it, send yourself a test mail and look at the headers. They should show dkim=pass, spf=pass and dmarc=pass.

For DMARC, roll out in steps: start with p=none and read the reports for a week or two. Once nothing legitimate fails, move to p=quarantine, and later to p=reject. Then mail that pretends to come from your domain, like a fake invoice to a client, ends up in spam or gets rejected.

5. The dev server: no public ports at all

I also work from a small VPS. It hosts no websites, so it doesn’t need to be reachable from the internet at all:

  • The firewall only allows traffic over the private network (Tailscale), and the provider’s firewall has no inbound rules.
  • SSH listens only on the private network address, accepts keys only and refuses root logins.
  • X11 and TCP forwarding are off, so a stolen login can’t be turned into a tunnel.
  • Security updates install themselves.
  • On the Tailscale side, SSH needs a fresh login confirmation regularly, and root logins aren’t allowed.
  • Only devices in active use stay in the private network, because every device in it can reach the server.

6. GitHub and CI: small keys

The server talks to GitHub with a fine-grained token that only reaches the one repository I work on there, instead of a broad token with access to everything. If a token ever ends up somewhere it doesn’t belong, like a chat or a log, treat it as leaked and regenerate it right away.

The CI pipeline follows the same idea: GitHub Actions are pinned to exact commits, so a moved tag can’t swap the code. The workflow token is read-only, and Dependabot proposes updates every week.

The short version

  • Site: strict CSP with hashes, HSTS, no inline styles or event handlers.
  • Cloudflare: Always Use HTTPS, TLS 1.2 minimum.
  • Domain: DNSSEC, CAA, certificate alerts, 2FA at the registrar.
  • Mail: SPF, DKIM (DNS only!), DMARC introduced in stages, starting with none.
  • Server: no public ports, SSH over a private network only, keys only.
  • Access: tokens as narrow as possible, and regenerated immediately if one leaks.

Each of these layers closes a different gap, and they only really protect you together. Every setup is different: other hosting, another mail provider, another registrar, and different people who need access. If you’d like me to review and harden your website the same way, get in touch.