Skip to content

Instantly share code, notes, and snippets.

@cofob
Last active May 28, 2025 17:37
Show Gist options
  • Save cofob/9b1fd205e6d961a45c225ae9f0af1394 to your computer and use it in GitHub Desktop.
Save cofob/9b1fd205e6d961a45c225ae9f0af1394 to your computer and use it in GitHub Desktop.

NixOS Binary Cache Proxy

nixos-cache-proxy.cofob.dev is a simple reverse proxy to cache.nixos.org, running over the Cloudflare network and utilizing its cache.

It is intended for use if you:

  1. Do not have access to cache.nixos.org (e.g., your government has blocked it).
  2. You experience low speed on cache.nixos.org.

How to Use It

Simply replace https://cache.nixos.org with https://nixos-cache-proxy.cofob.dev in the Nix configuration. For example:

nix.settings.substituters = lib.mkForce [
  "https://nixos-cache-proxy.cofob.dev"
];

Safety

You can trust this proxy because all data is signed with the cache.nixos.org key. This ensures that the proxy cannot alter any data—it can only serve what is already signed and verified. However, be aware that the proxy can see which data you are requesting, as it forwards your requests to cache.nixos.org.

Bunny.net CDN

The domain nixos-bunny-proxy.cofob.dev previously operated on Bunny.net CDN (User -> Bunny.net -> Cloudflare proxy). This Bunny proxy has been disabled. Please use the Cloudflare proxy (nixos-cache-proxy.cofob.dev) instead.

export default {
async fetch(request, env, ctx) {
if (request.method !== "GET") {
return new Response("Only GET requests are allowed.", {
status: 405, // Method Not Allowed
headers: { Allow: "GET" },
});
}
let url = new URL(request.url);
// Define allowed path regex patterns
const allowedPaths = [
/^\/$/, // /
/^\/nix-cache-info$/, // /nix-cache-info
/^\/[a-z0-9]+\.narinfo$/, // /[a-z0-9]+.narinfo
/^\/nar\/[a-z0-9]+\.nar(\.[a-z0-9]{2,4})?$/, // /nar/[a-z0-9]+.nar(.{2})?
];
// Check if the path matches any of the allowed patterns
const isAllowed = allowedPaths.some((regex) => regex.test(url.pathname));
if (!isAllowed) {
return new Response("Forbidden: Path not allowed.", {
status: 403, // Forbidden
});
}
let cache = caches.default; // Default Cloudflare cache
// Remove query string
url.search = "";
// Update protocol and host
url.protocol = "http:";
url.host = "cache.nixos.org";
// Check if the response is already cached
let cachedResponse = await cache.match(request);
if (cachedResponse) {
return cachedResponse;
}
// Fetch from origin
const originResponse = await fetch(url.href);
let response;
// Modify response for `/` route
if (url.pathname === "/") {
const originalBody = await originResponse.text();
const modifiedBody = originalBody.replace(/cache\.nixos\.org/g, "nixos-cache-proxy.cofob.dev");
response = new Response(modifiedBody, {
status: originResponse.status,
statusText: originResponse.statusText,
headers: originResponse.headers,
});
} else {
response = originResponse;
}
const updatedHeaders = new Headers(response.headers);
// Filter headers to remove those starting with "x-" and replace Cache-Control
for (let [key, value] of response.headers) {
if (key.toLowerCase().startsWith("x-") || key.toLowerCase() === "cache-control") {
updatedHeaders.delete(key);
}
}
// Adjust caching for 404 responses
if (response.status === 404) {
updatedHeaders.set("Cache-Control", "public, max-age=3600"); // 1 hour cache
} else {
updatedHeaders.set("Cache-Control", "public, immutable, max-age=31536000");
}
// Create a new response with updated headers
const finalResponse = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: updatedHeaders,
});
// Cache the response clone for future requests
ctx.waitUntil(cache.put(request, finalResponse.clone()));
return finalResponse;
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment