Creating an anonymous Google Fonts Proxy

Published by マギルゥーベルベット on

In this little guide I will show a way how to get rid of some Google domains for your website visitors. Note that your server does the requests instead of your visitor, so this adds some overhead to your server. Make sure that the server can handle this before continuing with this guide.

Requirements

  • A web server with proxy support (must not forward the client IP to make it really anonymous for your visitor)
  • A tiny NodeJS express server script to rewrite CSS responses from Google
  • Your own domain and the ability to add new subdomains
  • Optimal: a CDN like Cloudflare to reduce server load

NodeJS express URL rewriter for the CSS responses

const address = process.env.ADDRESS || "localhost";
const port = process.env.PORT || 60111;
const express = require("express");
const httpreq = require("httpreq");

const fontRouter = express.Router();

fontRouter["get"]("/*", function(req, res) {
    let errors = [];
    let params, rep;

    let baseUrl = req.get("X-Proxy-BaseUrl");
    baseUrl = baseUrl ? baseUrl : "your-domain-here.com";

    const requestUrl = "http://fonts.googleapis.com/css" + req.originalUrl.substring(req.originalUrl.indexOf('?'));

    httpreq.get(requestUrl, function(err, response) {
        res.setHeader("Content-Type", "application/json");
        if (err) {
            res.status(500).send({error: err});
        } else {
            res.setHeader("Content-Type", "text/css");

            if (response.statusCode !== 200) {
                res.status(response.statusCode).send(response.body);
                return;
            }

            let cssString = response.body;
            cssString = cssString.replace(/http.*\:\/\/fonts\.gstatic\.com/g, "https://google-gstatic-proxy." + baseUrl);
            res.status(200).send(cssString);
        }
    });
});

const app = express();
app.use("/css", fontRouter);
app.listen(port, address);

Adjust the base URL to your domain and copy this script anywhere on your server and give it an obvious name. By default it will listen on 127.0.0.1:60111, but you can change it to anything you want.

Web server configuration (Caddy)

# fonts.googleapis.com
google-fonts-proxy.your-domain-here.com {
    tls your-email-here
    tls {
        dns cloudflare
    }
    gzip
    proxy / http://127.0.0.1:60111
}

# fonts.gstatic.com <-- above host redirects here after rewriting the CSS
google-gstatic-proxy.your-domain-here.com {
    tls your-email-here
    tls {
        dns cloudflare
    }
    gzip
    proxy / https://fonts.gstatic.com/
}

The basic idea here is to proxy the first subdomain to the server started by NodeJS, and the second subdomain to gstatic where all the fonts are hosted. This proxy configuration doesn’t forward anything from the client to the target host. In other words, this proxy is completely anonym for your visitors. It even works through the Great Chinese Firewall, which makes it possible to serve Google Fonts to Chinese visitors.

WordPress Configuration

Once the proxy is setup, one more step needs to be taken to make all themes and plugins use the new domain(s). This change is a bit tricky and requires shell access to your server. This change also needs to be repeated every time a theme or plugin updates.

1. Navigate to the WordPress installation directory.

2. Navigate to wp-content

3. Make a backup of the plugins and themes directories.

4. Run the following snippet inside that directory. You need write access, so make sure to run this command with a privileged user.

GNU sed (Linux):

find plugins themes -type f \( ! -name "*.*" -o -iname "*.php" -o -iname "*.js" -o -iname "*.css" \) -exec sed -i 's/fonts\.googleapis\.com/google-fonts-proxy\.your-domain-here\.com/g' {} +

sed (BSD, macOS):

find plugins themes -type f \( ! -name "*.*" -o -iname "*.php" -o -iname "*.js" -o -iname "*.css" \) -exec sed -i '' 's/fonts\.googleapis\.com/google-fonts-proxy\.your-domain-here\.com/g' {} +

This command finds and replaces all Google Fonts API domains with the one from the proxy and saves the files in place. In case something goes wrong you can always restore from the backups. The downside right now is that you need to run this command every single time a plugin or theme updates.

If you have any thoughts, suggestions or how this can be improved, let me know. I’m active on Twitter and Reddit. :)