Google Fonts routes your visitors’ IPs through fonts.googleapis.com and fonts.gstatic.com on every page load — there’s no opt-out if you use the hosted CSS directly. I built a small nginx Docker image that sits in front of Google’s font infrastructure, rewrites the CSS on the fly, and serves everything from your own domain — visitors never connect to Google directly.
The image is published on Docker Hub as sbstjn/fonts; sources are on Codeberg.
How It Works
Google Fonts involves two separate hosts: fonts.googleapis.com serves the CSS stylesheet that lists the font faces, and fonts.gstatic.com serves the actual font binary files (woff2, etc.). The CSS contains URLs like //fonts.gstatic.com/s/dmsans/…. If you point a visitor at the CSS but let them fetch the font files directly from gstatic, you’ve only moved half the problem.
google-fonts-proxy handles both hosts:
- CSS endpoint (
/css2) — proxied tofonts.googleapis.com/css2. nginx rewrites the response body withsub_filterto replace every//fonts.gstatic.com/with//your-host/, so the stylesheet now points at your proxy for font files too. - Font files (
/s/…) — proxied tofonts.gstatic.com/s/…. Binary pass-through; no rewriting needed.
The nginx template handles both locations:
location = ${FONTS_CSS_LOCATION} {
proxy_pass https://fonts.googleapis.com/css2;
proxy_set_header Host fonts.googleapis.com;
proxy_set_header Accept-Encoding "";
proxy_hide_header Cache-Control;
proxy_hide_header Expires;
proxy_hide_header ETag;
proxy_hide_header Last-Modified;
add_header Cache-Control "public, max-age=31536000, immutable" always;
sub_filter_types text/css;
sub_filter '//fonts.gstatic.com/' '//${FONTS_PUBLIC_HOST}${FONTS_PATH_PREFIX}/';
sub_filter_once off;
}
location ^~ ${FONTS_STATIC_LOCATION_PREFIX}/ {
proxy_pass ${FONTS_STATIC_PROXY_PASS};
proxy_set_header Host fonts.gstatic.com;
proxy_hide_header Cache-Control;
proxy_hide_header Expires;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
The template variables (${FONTS_CSS_LOCATION}, ${FONTS_PUBLIC_HOST}, etc.) are substituted at container start by the official nginx entrypoint’s envsubst step, driven by environment variables.
Getting Started
Build and run the image locally. Set FONTS_PUBLIC_HOST to the hostname clients will use to reach the proxy — this is the value written into the rewritten CSS:
$ > docker build -t google-fonts-proxy .
$ > docker run \
-p 80:80 \
-e FONTS_PUBLIC_HOST=fonts.example.com \
google-fonts-proxy
With the container running, point your site’s font stylesheet at the proxy instead of Google directly. The URL format is identical to the Google Fonts CSS API:
<!-- Before: direct to Google -->
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;700&display=swap"
/>
<!-- After: through your proxy -->
<link
rel="preconnect"
href="https://fonts.example.com"
crossorigin
/>
<link
rel="stylesheet"
href="https://fonts.example.com/css2?family=DM+Sans:wght@400;700&display=swap"
/>
The proxy fetches the CSS from Google, rewrites the font file URLs to point at itself, and the browser fetches fonts from your domain — never touching Google’s infrastructure.
The Docker Hub image is available without building locally:
$ > docker pull sbstjn/fonts
Environment Variables
The container is configured via four environment variables:
| Variable | Default | Purpose |
|---|---|---|
FONTS_PUBLIC_HOST | fonts.example.com | Hostname written into rewritten CSS |
FONTS_PUBLIC_PATH | / | URL path prefix. / serves /css2 and /s/…. /fonts shifts everything under /fonts/css2 and /fonts/… |
FONTS_PORT | 80 | Port nginx listens on |
FONTS_REDIRECT_HOME | (unset) | If set, GET / returns 301 to this URL |
Deploying with OpenTofu on Scaleway
I deploy this image as a Scaleway Serverless Container with minimal resources — the OpenTofu configuration is straightforward:
resource "scaleway_container" "fonts" {
name = "fonts"
namespace_id = scaleway_container_namespace.main.id
registry_image = "${scaleway_container_namespace.main.registry_endpoint}/fonts:latest-amd64"
port = 80
cpu_limit = 100
memory_limit = 128
min_scale = 1
max_scale = 1
timeout = 10
http_option = "redirected"
privacy = "public"
deploy = true
environment_variables = {
FONTS_PUBLIC_HOST = "fonts.example.com"
FONTS_REDIRECT_HOME = "https://example.com/"
}
}
Push the image to your Scaleway container registry before applying. The container registry and namespace setup is covered in Deploy Serverless Containers to Scaleway with OpenTofu.
That’s It! 🎉
google-fonts-proxy routes all font traffic through your own infrastructure. Google only ever sees the proxy server’s IP — client IPs stay private and your visitors get the same fonts without any change to the user experience.
The image is available on Docker Hub (sbstjn/fonts) and the sources are on Codeberg.