|

What’s Your Privacy Policy?

The Privacy Wins Nobody Talks About
blog-privacy-hardening.html

The Privacy Wins Nobody Talks About

Every blog post you publish about privacy, security, or self-hosting? Your own site is probably undermining it while your readers read it.

I’m not talking about malware or breaches. I’m talking about the quiet stuff — the third-party requests that fire on every single pageview, sending your visitors’ IP addresses to companies they never consented to talk to. Google Fonts. Google Analytics. CDN-hosted jQuery. Gravatar. Each one is a tiny data leak that most site owners never even think about.

I run an InfoSec-themed homelab blog. If my site was leaking visitor data to Google on every pageview, that’s not just ironic — it’s embarrassing.

So I audited TheDeLay.com, found the leaks, and plugged them. Here’s what I changed, why it matters, and how you can do the same in an afternoon.

Just want the code? Jump to The Fix: Self-Hosting Your Fonts for the step-by-step, or grab the downloadable @font-face template at the bottom.

$ curl -sI thedelay.com | grep -i “third-party”

The Audit: What’s Your Site Actually Doing?

Before you fix anything, you need to know what’s broken. Most bloggers have no idea how many third-party requests their site makes on every pageview. Here’s how to find out in about 60 seconds.

Open DevTools Network Tab

Open your site in Chrome or Firefox. Press F12. Click the Network tab. Hard refresh with Ctrl+Shift+R. Watch the waterfall fill up.

// every line in that waterfall is your visitor’s browser talking to someone

Count the Domains

Every unique domain that isn’t yours is a third-party request. Each one means your visitor’s browser is sending their IP address, referrer URL, and user agent string to that company. Filter by “3rd-party” in Chrome DevTools to see them isolated.

Check the Usual Suspects
  • fonts.googleapis.com + fonts.gstatic.com — Google Fonts
  • www.google-analytics.com — Google Analytics
  • www.gravatar.com — WordPress comment avatars
  • cdn.jsdelivr.net or cdnjs.cloudflare.com — CDN-hosted scripts
  • connect.facebook.net — Facebook pixel
  • platform.twitter.com — Twitter/X embeds

Or skip the GUI and do it from a terminal:

# List every unique external domain your site contacts
curl -s https://yoursite.com | grep -oP 'https?://[^/"]+' | sort -u
Why this matters: Each third-party domain gets your visitor’s IP address, the page URL they’re reading, and their browser fingerprint. Even if you don’t track visitors, you might be letting three other companies do it on every pageview.

What I Found on TheDeLay.com

Before I started cleaning up, my site was making these third-party requests:

fonts.googleapis.com CSS file describing JetBrains Mono font ELIMINATED
fonts.gstatic.com Actual woff2 font files (~73KB) ELIMINATED
Cloudflare (CDN) All traffic proxied for DDoS/SSL CONSCIOUS CHOICE

Two requests to Google on every pageview. Not terrible by WordPress standards — I’ve seen sites with 15+ third-party domains. But not acceptable for a site with an InfoSec category and a privacy policy that brags about respecting visitor data.


$ strings /usr/share/fonts/google-fonts.woff2

Google Fonts: The Privacy Problem Nobody Mentions

Google Fonts powers over 60 million websites. It’s free, fast, and has excellent fonts. What’s not to love?

This: every time a visitor loads your page, their browser sends a request to Google’s servers. That request includes:

  • Their IP address — Google knows they visited your site
  • The referrer URL — Google knows which page they visited
  • Their user agent — Google knows their browser, OS, and device
  • Any existing Google cookies — potentially linking the visit to a Google account

Google says they log this data. They say they don’t use it for ad targeting. Maybe that’s true today. But you’re trusting Google with your visitors’ browsing behavior on every single pageview, and your visitors never consented to that relationship.

Put it this way: If you use Google Fonts on a personal blog, you’re giving Google a complete log of every page every visitor reads on your site. For a free font. That you could host yourself in 15 minutes.
The Legal Precedent (GDPR)

In January 2022, a Munich regional court (Az. 3 O 17493/20) fined a website operator €100 for transmitting a visitor’s IP address to Google via Google Fonts without explicit consent. The violation was under Art. 6(1) GDPR — no legal basis for the data transfer to Google.

The fine was symbolic. The precedent was not. It established that loading Google Fonts from Google’s CDN constitutes a data transfer requiring consent under GDPR. Several follow-up cases and mass-mailing campaigns from law firms targeting Google Fonts users followed in Germany and Austria.

Even if you’re not in the EU, the principle applies: you’re sending your visitors’ data to a third party without telling them. That’s worth fixing regardless of jurisdiction.


$ cp -r fonts.googleapis.com ./assets/fonts/

The Fix: Self-Hosting Your Fonts

Here’s exactly what I did to eliminate Google from TheDeLay.com’s font loading. The entire process took about 15 minutes. It works for any Google Font.

Find What You’re Loading

Check your theme’s functions.php or <head> for the Google Fonts URL. Mine looked like this:

# In functions.php:
wp_enqueue_style( 'kadence-child-fonts',
    'https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap'
);

// the URL tells you which font, weights, and styles are being loaded

Fetch the CSS to Find the woff2 URLs

Hit that Google Fonts URL with curl and a modern User-Agent header:

curl "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" \
  -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) Chrome/120"

The User-Agent header matters — Google serves different formats based on your browser. A modern UA gets woff2, which is the smallest and best-compressed format.

The response is CSS with @font-face blocks, each containing a src: url() pointing to fonts.gstatic.com. Those are the files you need to download.

// pro tip: Google often serves variable fonts — one file covers a range of weights (e.g., 400–700). Fewer files than you’d expect.

Download the Font Files
mkdir -p assets/fonts/

# Download each woff2 URL from the CSS response
curl -o assets/fonts/JetBrainsMono-Regular-latin.woff2 \
  "https://fonts.gstatic.com/s/jetbrainsmono/v21/..."

curl -o assets/fonts/JetBrainsMono-Italic-latin.woff2 \
  "https://fonts.gstatic.com/s/jetbrainsmono/v21/..."

# Repeat for latin-ext variants if present

My four files totaled 73KB — two for latin (normal + italic) and two for latin-ext (accented characters). That’s smaller than a typical hero image.

Write the @font-face Declarations

Add these to your theme’s style.css, replacing the Google Fonts <link>:

/* Latin (the file 95% of visitors need) */
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 400 700;          /* variable font: one file, multiple weights */
    font-display: swap;            /* show fallback text immediately */
    src: url('./assets/fonts/JetBrainsMono-Regular-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                   U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                   U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Latin italic */
@font-face {
    font-family: 'JetBrains Mono';
    font-style: italic;
    font-weight: 400;
    font-display: swap;
    src: url('./assets/fonts/JetBrainsMono-Italic-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, ...;  /* same range as above */
}

// unicode-range tells the browser “only download this file if the page contains characters in this range.” Most visitors load one woff2 file, not four.

Remove the Google Fonts Enqueue

In functions.php, delete or comment out the line that loads from Google:

// BEFORE (sends every visitor's IP to Google):
wp_enqueue_style( 'my-fonts',
    'https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap'
);

// AFTER (fonts load from your own server):
// JetBrains Mono is self-hosted via @font-face in style.css
Add a Preload Hint for Performance

Tell the browser to start fetching the primary font immediately, before it even parses the CSS:

// Add to functions.php
function mytheme_preload_fonts() {
    $font_url = get_stylesheet_directory_uri()
        . '/assets/fonts/JetBrainsMono-Regular-latin.woff2';
    echo '<link rel="preload" href="' . esc_url( $font_url )
        . '" as="font" type="font/woff2" crossorigin>';
}
add_action( 'wp_head', 'mytheme_preload_fonts', 1 );

// only preload the latin normal weight — it’s the one used on every page. The browser fetches italic and extended on demand.

The Result

Before

  • 2 requests to Google per pageview
  • Visitor IP sent to fonts.googleapis.com
  • Referrer URL disclosed to Google
  • GDPR consent arguably required

After

  • Zero requests to Google
  • Fonts served from own server (73KB total)
  • Preloaded for instant rendering
  • No consent needed — no data transfer

Visually identical. Functionally identical. font-display: swap ensures text is visible immediately while the font loads. The only difference is your visitors’ data stays on your server.


$ diff google-analytics.js umami.js

Cookie-Free Analytics: Ditching Google Analytics

Google Analytics is the default choice for website analytics. It’s also a privacy wreck — tracking cookies, cross-site identification, data fed into Google’s advertising machine. If you’re serious about visitor privacy, it’s the first thing that should go.

I replaced it with Umami — a self-hosted, open-source analytics platform. Here’s the trade-off in plain terms.

What I Gained

  • Page views, referrers, devices, countries
  • A clean dashboard I own and control
  • GDPR compliance without cookie banners
  • Complete data ownership (my PostgreSQL)

What I Gave Up

  • Conversion funnels (irrelevant for a blog)
  • Cross-site tracking (a “feature” I didn’t want)
  • Zero-effort setup (Umami needs Docker)
  • Granular real-time visitor counts
The math: Google Analytics gives you a fire hose of data you’ll never use, in exchange for handing Google a complete picture of your visitors’ behavior. Umami gives you the 20% of data that answers 95% of the questions a blog owner actually asks.
Service Cost Your Visitors’ Data
Google Analytics Free Is the product
Umami (self-hosted) Free You own everything
Plausible (cloud) $9/month EU-hosted, privacy-first
Fathom (cloud) $14/month Privacy-first, simple

The tracking code is one line. No cookies. No consent popups. No visitor data leaving your infrastructure.

<script defer src="https://analytics.yourdomain.com/script.js"
        data-website-id="your-website-id"></script>
Self-Hosting Umami: The Quick Version

Umami runs as a Docker container with PostgreSQL (or MySQL). The setup is straightforward:

  1. Pull the Docker image: docker pull ghcr.io/umami-software/umami:postgresql-latest
  2. Set up a docker-compose.yml with Umami + PostgreSQL 15
  3. Set DATABASE_URL and APP_SECRET environment variables
  4. Put it behind a reverse proxy (Apache/Nginx) with SSL
  5. Add the one-line tracking script to your theme

The whole thing takes about 30 minutes if you already have Docker running. Umami’s documentation covers it well.


$ cat /var/www/html/privacy-policy.html | wc –words

Writing a Privacy Policy That Isn’t Garbage

Most privacy policies are 3,000 words of legal copy-paste that nobody reads and nobody understands. They exist for compliance theater, not for actually telling people what’s happening with their data.

Here’s my approach: write it like a human, for humans.

When I wrote TheDeLay.com’s privacy policy, I structured it around one question per service: What data does this touch, and why?

For each third-party service, I documented:

  • What it does — the actual purpose, in plain language
  • What data it collects — specifically, not vaguely
  • Where that data goes — which company, which servers
  • My honest take — why I use it, or why I eliminated it

The result reads like a technical disclosure, not a legal document. It has collapsible sections for cookie details. It explicitly brags about the things I don’t collect. And it’s styled in the same terminal theme as the rest of the site, because your privacy policy shouldn’t look like it was written by a different company.

The Privacy Policy Litmus Test

  • Can a non-technical friend read it and understand what your site does with their data?
  • Does it list specific services by name, not just “third-party providers”?
  • Does it explain why each service is there, not just that it’s there?
  • Would you be comfortable if a journalist quoted it?

If any of those answers are no, rewrite it. Your privacy policy is a contract with your readers. Make it one you’d actually want to sign.


$ whois cloudflare.com

The Cloudflare Question

I’m going to be honest about this one: I use Cloudflare. All traffic to TheDeLay.com passes through Cloudflare’s network. That means Cloudflare sees every visitor’s IP address, every request, every page load.

This is a conscious trade-off.

What I Get What I Give Up
DDoS protection Cloudflare sees all traffic
Free SSL at the edge MITM-in-the-middle by design
Global CDN Content cached on their infra
Web application firewall Cloudflare decides what’s “malicious”
DNS management Domain resolves through their nameservers

I’m comfortable with this trade-off for three reasons:

  1. Business model alignment. Cloudflare sells security services, not advertising. They aren’t incentivized to profile my visitors.
  2. Practicality. Running my own edge protection for a personal blog isn’t realistic. The complexity and cost don’t make sense.
  3. Disclosure. I say so in my privacy policy. No hiding.
The key distinction: Perfect privacy on the modern internet doesn’t exist. The goal isn’t zero third-party contact — it’s conscious choices about which third parties you trust, and honest disclosure of those choices to your visitors. Know what you’re trading, own the trade-off, and tell your readers.

$ cat lessons-learned.log

Lessons Learned

Check Your Assumptions About “Free”

Google Fonts is free because Google is collecting browsing data across 60+ million websites. The currency is information, not money. Every “free” service has a business model — if you can’t see it, you’re the product. Or more accurately, your visitors are.

Look at the Network Tab Before the Code

I assumed my site was clean because I hadn’t intentionally added any trackers. The Google Fonts leak was inherited from the theme setup, and I didn’t notice it for weeks. The DevTools network tab doesn’t lie — start there, not in the source code.

Audit Regularly, Not Just Once

Every plugin update, theme change, or new widget can introduce third-party requests you didn’t authorize. I’ve made it a habit: after any change, open DevTools, check the network tab, verify no new external domains appeared. Takes 30 seconds.

Unicode-Range Is Your Friend

When self-hosting fonts, keep Google’s unicode-range values in your @font-face declarations. This enables automatic subsetting — the browser only downloads the Latin Extended file if the page actually contains accented characters like á or ü. Most visitors load one woff2 file, not four.

Don’t Let Perfect Be the Enemy of Good

I still use Cloudflare. I’m not running a Tor hidden service. But I eliminated every unnecessary data leak I could find, and I’m transparent about the ones that remain. Moving from “leaking data to Google on every pageview” to “one conscious CDN choice, fully disclosed” is a massive improvement.

Explain Your Choices

A privacy policy that says “we use cookies” is useless. A privacy policy that says “Cloudflare sees your IP because we need DDoS protection, and here’s their data retention policy” is useful. Be specific. Be honest. Respect your readers enough to tell them what’s actually happening.


$ cat self-host-fonts-template.css

Grab the Template

I’ve packaged the @font-face declarations, preload function, and Google Fonts removal code into a single CSS template file. It’s annotated with comments explaining every line, and works with any Google Font — just swap the font name and file paths.


$ exit

Your Turn

Audit your site. Open DevTools. Count the third-party domains. Then ask yourself: does each one need to be there?

The font self-hosting took me 15 minutes. The analytics swap took an afternoon. The privacy policy took a couple of hours. The total cost was $0.

Your visitors will never know you did any of this. That’s the whole point — privacy done right is invisible. But you’ll know. And you’ll be able to look at your network tab without cringing.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *