Your site got flagged. A German user filed a complaint. Or maybe your lawyer forwarded you a document you didn’t fully understand, but the words “third-party data transfer” were highlighted in yellow. Now you’re staring at a <link> tag in your <head> that connects to fonts.googleapis.com, and you realize that every time someone loads your page, their IP address gets quietly shipped off to Google’s servers — servers that sit outside the EU.
This isn’t a hypothetical scare. German courts have been issuing rulings against websites using Google Fonts via CDN since at least 2022. One case alone resulted in a €100 fine for a single visitor’s IP being transmitted to Google without consent. That sounds small until you multiply it by your monthly traffic and realize that “per visitor” is the operative phrase. I’m Rohan Ratnayake, and I’ve spent the last 5 years as a web performance and privacy compliance engineer working specifically with WordPress and custom-stack sites that needed to pass GDPR audits. I’ve watched site owners swap out their analytics, add cookie banners, update their privacy policies — and then completely miss the Google Fonts CDN call sitting right there in the source code.
The mistake I see constantly isn’t laziness. It’s that most tutorials only cover the <link> method — grab a snippet from Google Fonts, paste it into your header, done. Nobody walks you through what happens after you decide to stop using it. This article fixes that. I’ll show you how to download the actual font files, convert them properly, host them on your own server, and write the CSS that makes it all work — no CDN, no third-party request, no privacy exposure.
Why the Standard Google Fonts Embed Breaks GDPR
When you use the standard embed, your visitor’s browser makes a live HTTP request to fonts.googleapis.com. Google logs that request, which includes the visitor’s IP address. Under GDPR Article 44, transferring personal data — and an IP address qualifies — to a third country (the US) requires either explicit consent or a valid legal basis. Most sites have neither for a font request.
The fix isn’t to add a consent banner for fonts. The fix is to stop the request from happening at all.
Step 1: Download the Font Files (Not the Embed Code)
Go to Google Fonts and pick your typeface. On the family page, click Download family in the top right. This gives you a .zip with the raw .ttf files.
Here’s where most people stop and make their first mistake: they upload those .ttf files directly to their server. Don’t. The .ttf format is fine for operating systems, but for web delivery, .woff2 is what you want. It’s the most compressed modern web font format, and browser support is effectively universal for any browser released after 2016.
What you get in the downloaded .zip vs. what you actually need:
| File Format | Typical File Size (for one weight) | Browser Support | Use For |
|---|---|---|---|
.ttf | 150–300 KB | Broad but unoptimized | Desktop apps, fallback |
.woff | 80–160 KB | IE9+, all modern | Legacy browser support |
.woff2 | 60–120 KB | All modern browsers (97%+) | Primary web delivery |
Unless you’re supporting IE11, you only need .woff2. Carrying both .woff and .woff2 adds weight for users whose browsers already support the better format.
Step 2: Convert .TTF to .WOFF2

The tool I use consistently is google-webfonts-helper at gwfh.mranftl.com. You type the font name, choose your weights and character subsets, and it generates the .woff2 files plus the complete CSS snippet. No command line required. This is particularly useful because it also handles subsetting — so if you only need Latin characters, you’re not loading a file bloated with Cyrillic or Greek glyphs you’ll never display.
If you’re working on a project where you need to batch-process multiple fonts or want offline control, fonttools via Python handles this cleanly:
pip install fonttools brotli
fonttools ttLib.woff2 compress MyFont-Regular.ttf
That outputs MyFont-Regular.woff2 in the same directory. Run it per file, per weight.
Common weights you’ll encounter and when to include them:
| Font Weight Value | CSS Name | When You Actually Need It |
|---|---|---|
| 400 | Regular | Always — body text |
| 500 | Medium | Navigation, UI labels |
| 600 | Semi-Bold | Subheadings, emphasis |
| 700 | Bold | H1, H2, strong CTAs |
| 300 | Light | Display text, pull quotes |
Don’t download all seven weights “just in case.” Each additional weight is a separate file request and adds to your page load. I had a client whose site was loading six font weights from Google’s CDN. We cut it to three, moved to self-hosting, and their font-related network payload dropped from 480 KB to 140 KB.
Step 3: Upload to Your Server
Create a /fonts/ directory in your web root or within your theme’s assets folder. Upload your .woff2 files there. The directory structure I use:
/assets/
/fonts/
inter-400.woff2
inter-600.woff2
inter-700.woff2
Keep naming consistent. I use [family-name]-[weight].woff2. It sounds obvious but when you come back to this six months later, inconsistent naming wastes time.
Step 4: Write Your @font-face CSS
This is the part most articles rush through, and it’s where subtle errors sneak in. Here’s a clean, complete @font-face block:
@font-face{
font-family: 'Inter';
src: url('/assets/fonts/inter-400.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face{
font-family: 'Inter';
src: url('/assets/fonts/inter-700.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
font-display: swap is doing real work here. It tells the browser to render text in a system fallback font immediately, then swap in your custom font once it loads. Without it, visitors with slower connections see blank text while the font loads — browsers call this FOIT (Flash of Invisible Text). Swap eliminates that.
Once your @font-face declarations are in place, use the font normally:
body {
font-family: 'Inter', sans-serif;
}
Step 5: Verify No External Requests Are Going Out

This step matters. A lot of people self-host the fonts and forget to remove the original <link> tag. I’ve audited sites that had both — local files loaded correctly, and the old CDN call still firing in the background. The GDPR risk doesn’t disappear just because the local fonts work; it disappears when the external request stops.
Open Chrome DevTools → Network tab → reload the page → filter by “font” or search for “google” in the request URLs. If you see anything pointing to fonts.googleapis.com or fonts.gstatic.com, you still have a live embed somewhere.
Where to look if you can’t find the source:
- Theme’s
functions.php(WordPress) — look forwp_enqueue_stylecalls with Google Fonts URLs - Page builder stylesheets (Elementor, Divi, Beaver Builder all have their own font settings panels)
- Plugin stylesheets — some plugins load their own Google Fonts independently
- Inline
<style>blocks injected by third-party scripts
I once spent 40 minutes hunting a Google Fonts request on a client site before finding it buried inside a contact form plugin’s admin CSS that was somehow loading on the frontend. Dequeue it or replace it. Don’t just assume the source is obvious.
Setting Cache Headers for Performance
Once you’re self-hosting, you control the cache. Set long cache expiry on font files since they never change unless you update them manually:
# Apache .htaccess
<FilesMatch "\.(woff2)$">
Header set Cache-Control "max-age=31536000, public, immutable"
</FilesMatch>
# Nginx
location ~* \.(woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
One year (31536000 seconds) is standard. Returning visitors won’t re-download the font at all after their first visit — it comes straight from their browser cache.
The Privacy Policy Update You Still Need to Make

Self-hosting removes the third-party data transfer, but you should still update your privacy policy to reflect that fonts are served from your own infrastructure. Remove any language that references Google Fonts as a third-party service. Your data processing records (Article 30 of GDPR) should reflect this change too if you maintain them. This isn’t legal advice — check with a qualified privacy attorney for your specific situation — but the documentation side of a compliance fix is just as real as the technical side.
FAQs
Will self-hosting slow my site down compared to Google’s CDN? Not meaningfully. Google’s CDN used to have a cache-sharing advantage — browsers had already cached fonts from other sites using the same Google CDN URL. That browser cache partitioning changed in Chrome 86 (2020) and Firefox 85 (2021), so that shared cache benefit no longer exists. Your self-hosted fonts load from the same server as your HTML and CSS, which is typically faster for your primary audience.
Do I need to host both .woff and .woff2 formats? Only if you’re supporting browsers from before 2016. For any site targeting modern users, .woff2 alone covers over 97% of browsers. Adding .woff as a fallback adds file management overhead without meaningful benefit in 2025.
Does self-hosting fonts affect my Google PageSpeed score? Yes, positively. Eliminating a render-blocking third-party request is one of the cleaner wins in a PageSpeed audit. Combined with font-display: swap, most sites see measurable improvement in their Largest Contentful Paint (LCP) score after switching.
Wrapping This Up
The core problem here is simple: a font request to an external server is a data transfer, and a data transfer to a US company without consent is a GDPR liability. Self-hosting cuts that chain entirely. Download the files, convert to .woff2, upload to your own server, write the @font-face CSS, and verify through DevTools that the external request is actually gone.
Your next step is to run your site through a network audit today — not next week. Open DevTools, filter for third-party font requests, and confirm you’ve removed every one. If you’re on WordPress and want to automate the removal of stray Google Fonts calls from plugins, the OMGF (Optimize My Google Fonts) plugin handles dequeuing automatically. But always verify with DevTools afterward. Tools can miss things. Your eyes won’t.

