发现新文章

1854 字
9 分钟
Github全站反向代理搭建指南
搭建Github全站代理的完整指南,涵盖原理讲解与多种部署方案(Cloudflare Worker、EdgeOne Pages、Vercel、VPS+Go)
2025年4月15日 00:00
0 次

引言#

由于网络原因,国内访问Github经常遇到各种问题。本文将带你从原理到实践,搭建一个属于自己的Github全站反向代理。

为什么不能只用透明代理?#

针对Github这样的网站,我们无法仅使用一个简单的透明反向代理指向 github.com 来解决,原因有两点:

1. 外域依赖问题#

Github官网有许多外域依赖,比如 raw.githubusercontent.comavatars.githubusercontent.com 等。如果只代理主域名,这些资源请求会直接访问原站,导致加载失败。

2. 钓鱼风险#

注意!直接反代主流网站后,不久你的网站就会被Cloudflare标记为钓鱼站点,因为你原封不动的克隆了人家站点并且 没有显式屏蔽登录页面

解决方案:透明代理 + HTML覆写#

核心思路#

我们需要实现两个关键功能:

  1. 透明代理:将请求转发到Github服务器
  2. HTML覆写:重写Github返回的HTML,将其中的外域改为我们自己的域

请求流程对比#

原始流程:

用户 -> github.com -> raw.githubusercontent.com(被github.com请求)

代理流程:

用户 -> gh.072103.xyz -> raw-githubusercontent-com.072103.xyz(被gh.072103.xyz请求)

对于 gh.072103.xyz 的请求由代理服务转发到 github.com,而针对于 raw-githubusercontent-com.072103.xyz 的请求则转发到 raw.githubusercontent.com

域名映射配置#

你需要配置类似这样的域名映射:

const domain_mappings = {
  'github.com': 'gh.',
  'avatars.githubusercontent.com': 'avatars-githubusercontent-com.',
  'github.githubassets.com': 'github-githubassets-com.',
  'collector.github.com': 'collector-github-com.',
  'api.github.com': 'api-github-com.',
  'raw.githubusercontent.com': 'raw-githubusercontent-com.',
  'gist.githubusercontent.com': 'gist-githubusercontent-com.',
  'github.io': 'github-io.',
  'assets-cdn.github.com': 'assets-cdn-github-com.',
  'cdn.jsdelivr.net': 'cdn.jsdelivr-net.',
  'securitylab.github.com': 'securitylab-github-com.',
  'www.githubstatus.com': 'www-githubstatus-com.',
  'npmjs.com': 'npmjs-com.',
  'git-lfs.github.com': 'git-lfs-github-com.',
  'githubusercontent.com': 'githubusercontent-com.',
  'github.global.ssl.fastly.net': 'github-global-ssl-fastly-net.',
  'api.npms.io': 'api-npms-io.',
  'github.community': 'github-community.'
};

假如你的域名为 abc.com,你需要将以下子域名绑定到你的代理服务:

  • gh.abc.com
  • avatars-githubusercontent-com.abc.com
  • raw-githubusercontent-com.abc.com
  • …等等

防钓鱼措施#

我们需要找到原站点的所有登录页逐一屏蔽,对于Github.com,我们需要屏蔽:

/ /login /signup copilot

你可以将其直接导向404,或者重定向到另外的网站,只要不让你的用户能在你的反代网站上登录就可以


部署方案#

下面介绍四种部署方案,按照部署难度从简单到复杂排序:

方案一:Vercel Function(最简单)#

嫌弃CF Worker不够快?那就试试Vercel Function!

优点#

  • 部署最简单,一键完成
  • 速度快
  • 与GitHub集成良好

部署步骤#

  1. 克隆 afoim/VercelFunctionGithubProxy

  2. 部署到Vercel

  1. 绑定你自己的域名

  1. 根据你的域名修改域名映射配置,绑定所有子域名即可使用

方案二:Cloudflare Worker(推荐)#

教程视频:https://www.bilibili.com/video/BV1jGd6YpE8z

优点#

  • 免费
  • 无需服务器
  • 全球CDN加速
  • 部署简单

部署步骤#

  1. 进入 dash.cloudflare.com

  2. 创建新Worker,选择Hello World模板

  3. 前往 GitHub - afoim/GithubSiteProxyForCloudflareWorker 复制 worker.js 代码粘贴到你的Worker

  4. 根据你的域名修改域名映射配置

  5. 将所有需要的子域名绑定到你的Worker

  6. 访问 gh.你的域名 即可使用

完整代码#

参见Github仓库:https://github.com/afoim/GithubSiteProxyForCloudflareWorker


方案三:EdgeOne Pages#

适合国内用户,访问速度更快

优点#

  • 国内访问速度优秀
  • 免费额度充足
  • 部署相对简单

部署步骤#

1. 下载源码#

源码:afoim/EdgeOnePagesFunctionGithubProxy

下载 https://r2.072103.xyz/github-eopf.zip 并解压

目录结构:

2. 修改域名配置#

打开任意一个JS文件,更改域名映射配置。注意:每个JS文件的内容都需要修改!

3. 上传到EdgeOne Pages#

4. 绑定域名#

按照前缀绑定所有需要的子域名:

为什么目录结构这么特殊?#

  • index.html:空的HTML文件,因为不放就404
  • index.js:负责 / 路由
  • [[default.js]]:负责 /* 路由

方案四:VPS + Go(最灵活)#

适合有VPS且希望完全掌控的用户,部署相对复杂

优点#

  • 完全自主可控
  • 不依赖第三方平台
  • 可以自定义更多功能

部署步骤#

1. 安装Golang运行时#

apt install golang

2. 创建项目目录#

创建一个文件夹,放置 main.go

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

// 域名映射配置
var domainMappings = map[string]string{
	"github.com":                    "gh.",
	"avatars.githubusercontent.com": "avatars-githubusercontent-com.",
	"github.githubassets.com":       "github-githubassets-com.",
	"collector.github.com":          "collector-github-com.",
	"api.github.com":                "api-github-com.",
	"raw.githubusercontent.com":     "raw-githubusercontent-com.",
	"gist.githubusercontent.com":    "gist-githubusercontent-com.",
	"github.io":                     "github-io.",
	"assets-cdn.github.com":         "assets-cdn-github-com.",
	"cdn.jsdelivr.net":              "cdn.jsdelivr-net.",
	"securitylab.github.com":        "securitylab-github-com.",
	"www.githubstatus.com":          "www-githubstatus-com.",
	"npmjs.com":                     "npmjs-com.",
	"git-lfs.github.com":            "git-lfs-github-com.",
	"githubusercontent.com":         "githubusercontent-com.",
	"github.global.ssl.fastly.net":  "github-global-ssl-fastly-net.",
	"api.npms.io":                   "api-npms-io.",
	"github.community":              "github-community.",
}

// 需要重定向的路径
var redirectPaths = []string{"/", "/login", "/signup", "/copilot"}

// 检查路径是否需要重定向
func shouldRedirect(path string) bool {
	for _, p := range redirectPaths {
		if path == p {
			return true
		}
	}
	return false
}

// 获取代理前缀
func getProxyPrefix(host string) string {
	if strings.HasPrefix(host, "gh.") {
		return "gh."
	}

	for _, prefix := range domainMappings {
		if strings.HasPrefix(host, prefix) {
			return prefix
		}
	}

	return ""
}

// 根据前缀获取目标域名
func getTargetHost(prefix string) string {
	for original, p := range domainMappings {
		if p == prefix {
			return original
		}
	}
	return ""
}

// 处理响应内容,替换域名引用
func modifyResponse(body []byte, contentType, hostPrefix, currentHostname string) []byte {
	if !strings.Contains(contentType, "text/") &&
		!strings.Contains(contentType, "application/json") &&
		!strings.Contains(contentType, "application/javascript") &&
		!strings.Contains(contentType, "application/xml") {
		return body
	}

	text := string(body)
	domainSuffix := currentHostname[len(hostPrefix):]

	for originalDomain, proxyPrefix := range domainMappings {
		fullProxyDomain := proxyPrefix + domainSuffix
		text = strings.ReplaceAll(text, "https://"+originalDomain, "https://"+fullProxyDomain)
		text = strings.ReplaceAll(text, "http://"+originalDomain, "https://"+fullProxyDomain)
		text = strings.ReplaceAll(text, "//"+originalDomain, "//"+fullProxyDomain)
		text = strings.ReplaceAll(text, `"`+originalDomain+`"`, `"`+fullProxyDomain+`"`)
		text = strings.ReplaceAll(text, `'`+originalDomain+`'`, `'`+fullProxyDomain+`'`)
	}

	if hostPrefix == "gh." {
		text = strings.ReplaceAll(text, `"/`, `"https://`+currentHostname+`/`)
		text = strings.ReplaceAll(text, `'/`, `'https://`+currentHostname+`/`)
	}

	return []byte(text)
}

// 处理请求
func handleRequest(w http.ResponseWriter, r *http.Request) {
	currentHost := r.Host

	if shouldRedirect(r.URL.Path) {
		http.Redirect(w, r, "https://www.gov.cn", http.StatusFound)
		return
	}

	hostPrefix := getProxyPrefix(currentHost)
	if hostPrefix == "" {
		http.Error(w, "Domain not configured for proxy", http.StatusNotFound)
		return
	}

	targetHost := getTargetHost(hostPrefix)
	if targetHost == "" {
		http.Error(w, "Domain not configured for proxy", http.StatusNotFound)
		return
	}

	pathname := r.URL.Path

	re1 := regexp.MustCompile(`(/[^/]+/[^/]+/(?:latest-commit|tree-commit-info)/[^/]+)/https%3A//[^/]+.*`)
	pathname = re1.ReplaceAllString(pathname, "$1")

	re2 := regexp.MustCompile(`(/[^/]+/[^/]+/(?:latest-commit|tree-commit-info)/[^/]+)/https://[^/]+.*`)
	pathname = re2.ReplaceAllString(pathname, "$1")

	re3 := regexp.MustCompile(`(/[^/]+/[^/]+/(?:latest-commit|tree-commit-info)/[^/]+)/https:/[^/]+.*`)
	pathname = re3.ReplaceAllString(pathname, "$1")

	targetURL := &url.URL{
		Scheme:   "https",
		Host:     targetHost,
		Path:     pathname,
		RawQuery: r.URL.RawQuery,
	}

	req, err := http.NewRequest(r.Method, targetURL.String(), r.Body)
	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to create request: %v", err), http.StatusInternalServerError)
		return
	}

	for key, values := range r.Header {
		for _, value := range values {
			req.Header.Add(key, value)
		}
	}

	req.Header.Set("Host", targetHost)
	req.Header.Set("Referer", targetURL.String())
	req.Header.Set("Accept-Encoding", "identity")

	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	resp, err := client.Do(req)
	if err != nil {
		http.Error(w, fmt.Sprintf("Proxy Error: %v", err), http.StatusBadGateway)
		return
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to read response: %v", err), http.StatusInternalServerError)
		return
	}

	contentType := resp.Header.Get("Content-Type")
	modifiedBody := modifyResponse(body, contentType, hostPrefix, currentHost)

	for key, values := range resp.Header {
		if key == "Content-Encoding" || key == "Content-Length" {
			continue
		}
		for _, value := range values {
			w.Header().Add(key, value)
		}
	}

	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Credentials", "true")
	w.Header().Set("Cache-Control", "public, max-age=14400")
	w.Header().Del("Content-Security-Policy")
	w.Header().Del("Content-Security-Policy-Report-Only")
	w.Header().Del("Clear-Site-Data")
	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(modifiedBody)))
	w.WriteHeader(resp.StatusCode)
	w.Write(modifiedBody)
}

func main() {
	http.HandleFunc("/", handleRequest)
	port := ":8080"
	log.Printf("GitHub代理服务器启动在端口 %s", port)
	log.Printf("请确保你的域名已正确配置并指向此服务器")

	if err := http.ListenAndServe(port, nil); err != nil {
		log.Fatal("服务器启动失败:", err)
	}
}

3. 创建 go.mod#

module github-proxy

go 1.19

4. 运行服务#

go run .

输出以下日志即成功:

root@localhost:~/go_proxy# go run .
2025/06/20 23:13:17 GitHub代理服务器启动在端口 :8080
2025/06/20 23:13:17 请确保你的域名已正确配置并指向此服务器

5. 配置Nginx反向代理#

使用Nginx或OpenResty反向代理 localhost:8080,配置域名格式为 gh.你的域名

6. 签发SSL证书#

签发泛域名证书并部署:

7. 完成#

现在你可以通过自己的域名+VPS代理访问Github,国内直连,无需梯子:


方案对比#

方案成本国内速度部署难度可定制性
Vercel Function免费一般最简单中等
Cloudflare Worker免费一般简单中等
EdgeOne Pages免费优秀简单中等
VPS + GoVPS费用取决于VPS位置复杂

高级配置#

如果你想修改三级域名,比如将 gh.abc.com 改为 github.abc.com,直接更改域名映射配置对应键的值即可。

你可以添加和删除要重定向的路径,默认重定向到一个神秘网站,根据注释自行更改。

本项目也是一个通用的全站反代模板,可以反代其他网站(注意需要大改代码)。

这篇文章是否对你有帮助?

发现错误或想要改进这篇文章?

在 GitHub 上编辑此页

文章修订历史 (19 次)

查看变更记录
4月4日 22:52:13 a694884

feat: 将所有存量文章时间统一减去8小时,修正时区偏移

3月10日 20:23 2e8ac90

chore: remove AI summaries from posts

3月1日 16:54:35 12ed72c

docs: 统一博客文章AI摘要模型为gemini-3-flash-preview并优化内容

2月25日 22:23:08 818735a

feat(posts): 为所有文章添加AI摘要并支持AI类型提示块

2月21日 21:02:26 ab007fe

docs: 重构并整合多个Github代理文章为完整指南

2025年8月1日 08:16:25 2f74587

!update: 将图源由R2改为Netlify

2025年7月29日 21:50:05 71eb437

posts: 更新gh-proxy.md中的代码引用方式为GitHub仓库链接 新增why-not-icp.md文章解释未备案原因

2025年7月26日 09:15:27 98609b5

feat: 更改域名为2x.nz

2025年7月19日 16:52:49 146fe9b

更改图源为EdgeOne

2025年7月19日 16:09:57 9bf0d90

chore: 更新图片域名从eo-r2.2x.nz到r2.afo.im

2025年7月19日 15:18:58 f5afef1

更改图源为EdgeOne

2025年7月18日 12:27:16 c7e56a3

1

2025年7月2日 03:54:49 34fdbf5

posts: 为Github Worker反代文章添加完整的Worker代码

2025年7月1日 23:29:28 4e3e7e8

posts: 更改图源CDN为Secbit MCDN,并且页脚放置赞助信息

2025年6月26日 21:07:29 b480111

update: 更新R2源。删除302

2025年6月25日 00:10:13 e2d1f68

update: 更新R2源

2025年5月16日 18:11:52 5178a56

更改图源URL

2025年4月30日 19:44:57 b36ee82

更新文章fuwari,修复错误的ICON配置教程,链接由Font Awesome改为icones.js.org,并且重新配图。更新文章gh-proxy和starrail-fast添加对应标签。更新文章static-web将标签Cloudflare Page改为Cloudflare Pages

2025年4月30日 08:38:25 d80a293

初始化

Github全站反向代理搭建指南
作者
二叉树树
发布于
2025年4月15日 00:00
许可协议
CC BY-NC-SA 4.0