Skip to content

NPM代理搭建指南

2024年11月16日

最近在家里开发项目的时候,时常碰到npm安装超时。想了一下手上有好多服务器,应该能解决这个问题,于是我搭建了一个代理服务器。

技术方案概述

npm registry有两个主要地址:

需要让npm客户端访问这两个域名的时候走自己的服务器,并突破封锁。

本方案概要:

  • 使用Nginx搭建反向代理服务器
  • 自签名SSL证书并设置信任
  • 使用TailScale VPN实现网络互通
  • hosts文件配置域名解析

其中TailScale是一个基于WireGuard的现代VPN解决方案,它是本方案中不可或缺的组件,因为它能够帮助我们:

  • 通过零配置组网快速建立安全通道
  • 利用 WireGuard 的高性能加密通信确保数据安全
  • 实现 NAT 穿透,突破网络封锁
  • 支持多平台部署

为什么不直接使用梯子:一方面是因为梯子流量有限,安装npm包会消耗大量流量;另一方面是我主要使用浏览器插件来分流,因此梯子没有配置按域名分流,无法针对npm的请求进行分流。

为什么不使用npm镜像:因为通过npm镜像安装的话,lock文件也会使用镜像地址,这样在CI/CD环境中可能会出现新的问题。

SSL证书配置与信任

为了确保npm客户端信任我们自己搭的反向代理,我们需要配置并信任自签名证书。整个过程分为三步:生成根证书(CA)、生成服务器证书、添加证书信任。

生成根证书(CA)

这个脚本完成以下工作:

  • 生成 2048 位的 CA 私钥
  • 创建证书签名请求(CSR)
  • 设置基本约束和密钥用途
  • 生成有效期为10年的CA证书
sh
#!/bin/bash
set -o errexit

# Generate CA private key
openssl genrsa -out ca.key 2048

# Generate CA certificate signing request
openssl req -new -key ca.key -out ca.csr -sha256 \
    -subj "/C=CN/ST=GuangDong/L=Shenzhen/O=Tinkink/OU=Tinkink/CN=tinkink.net"

# Create ca.ext file
echo "basicConstraints=CA:TRUE
keyUsage=keyCertSign,cRLSign" > ca.ext

# Generate CA certificate
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt \
    -extfile ca.ext -sha256 -days 3650
#!/bin/bash
set -o errexit

# Generate CA private key
openssl genrsa -out ca.key 2048

# Generate CA certificate signing request
openssl req -new -key ca.key -out ca.csr -sha256 \
    -subj "/C=CN/ST=GuangDong/L=Shenzhen/O=Tinkink/OU=Tinkink/CN=tinkink.net"

# Create ca.ext file
echo "basicConstraints=CA:TRUE
keyUsage=keyCertSign,cRLSign" > ca.ext

# Generate CA certificate
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt \
    -extfile ca.ext -sha256 -days 3650

生成服务器证书

这个脚本完成以下工作:

  • 生成服务器私钥
  • 创建服务器证书签名请求
  • 配置证书扩展信息,包括多域名支持
  • 使用之前创建的 CA 证书签发服务器证书
sh
#!/bin/bash

# Generate private key for npm
openssl genrsa -out npm.key 2048

# Generate certificate signing request
openssl req -new -key npm.key -out npm.csr -sha256 \
    -subj "/C=CN/ST=GuangDong/L=Shenzhen/O=Tinkink/OU=Tinkink/CN=registry.npmjs.org"

# Create npm.ext file
echo "authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName=@alt_names

[alt_names]
DNS.1=registry.npmjs.org
DNS.2=registry.npmjs.com" > npm.ext

# Generate certificate
openssl x509 -req -in npm.csr -CA ca.crt -CAkey ca.key \
    -CAcreateserial -out npm.crt -extfile npm.ext -sha256 -days 720
#!/bin/bash

# Generate private key for npm
openssl genrsa -out npm.key 2048

# Generate certificate signing request
openssl req -new -key npm.key -out npm.csr -sha256 \
    -subj "/C=CN/ST=GuangDong/L=Shenzhen/O=Tinkink/OU=Tinkink/CN=registry.npmjs.org"

# Create npm.ext file
echo "authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName=@alt_names

[alt_names]
DNS.1=registry.npmjs.org
DNS.2=registry.npmjs.com" > npm.ext

# Generate certificate
openssl x509 -req -in npm.csr -CA ca.crt -CAkey ca.key \
    -CAcreateserial -out npm.crt -extfile npm.ext -sha256 -days 720

证书信任配置

生成证书后,需要将 CA 证书(ca.crt)添加到系统的信任存储中:

Windows:

  1. 双击证书文件
  2. 选择"安装证书" -> "本地计算机"(需要管理员权限)
  3. 选择"受信任的根证书颁发机构"
  4. 也可以使用管理员权限运行 PowerShell 命令:
powershell
Import-Certificate -FilePath "ca.crt" -CertStoreLocation Cert:\LocalMachine\Root
Import-Certificate -FilePath "ca.crt" -CertStoreLocation Cert:\LocalMachine\Root

Mac:

  1. 双击证书文件,添加到钥匙串访问
  2. 在钥匙串访问中找到证书,双击展开
  3. 展开"信任"选项,将"使用此证书时"设置为"始终信任"

Linux:

bash
sudo cp ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
sudo cp ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

Nginx 反向代理配置

在服务器上先配置好TailScale VPN,确保本地和服务器在同一个网络中。然后使用以下 Nginx 配置文件:

nginx
server {
    listen 80;
    server_name registry.npmjs.com registry.npmjs.org;

    # Redirect all HTTP requests to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name registry.npmjs.com registry.npmjs.org;

    ssl_certificate /etc/nginx/ssl/npm.crt;
    ssl_certificate_key /etc/nginx/ssl/npm.key;

    ssl_protocols TLSv1.2 TLSv1.3;

    # Define the DNS resolver
    resolver 8.8.8.8 8.8.4.4 valid=30s; # You can use Google's public DNS or any resolver you prefer
    resolver_timeout 5s;

    # Proxy requests to npm registry
    location / {
        proxy_pass https://$host;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Proxy settings to handle large requests and timeouts
        proxy_read_timeout 90;
        proxy_connect_timeout 90;
        proxy_redirect off;

        # Optionally, if you need to handle large response or request sizes
        client_max_body_size 50M;
    }
}
server {
    listen 80;
    server_name registry.npmjs.com registry.npmjs.org;

    # Redirect all HTTP requests to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name registry.npmjs.com registry.npmjs.org;

    ssl_certificate /etc/nginx/ssl/npm.crt;
    ssl_certificate_key /etc/nginx/ssl/npm.key;

    ssl_protocols TLSv1.2 TLSv1.3;

    # Define the DNS resolver
    resolver 8.8.8.8 8.8.4.4 valid=30s; # You can use Google's public DNS or any resolver you prefer
    resolver_timeout 5s;

    # Proxy requests to npm registry
    location / {
        proxy_pass https://$host;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Proxy settings to handle large requests and timeouts
        proxy_read_timeout 90;
        proxy_connect_timeout 90;
        proxy_redirect off;

        # Optionally, if you need to handle large response or request sizes
        client_max_body_size 50M;
    }
}

这里重点说明两个关键配置:

  1. resolver配置用来指定当nginx访问npm registry时的DNS解析服务器,这里使用了Google的公共DNS服务器,如果不设置的话会报错
  2. proxy_pass https://$host:使用 $host 变量而不是硬编码域名,这样可以支持多个npm registry域名

hosts 文件配置

要让本地请求指向代理服务器,需要修改 hosts 文件。推荐使用SwitchHosts。但需要注意,大部分系统下,修改Hosts都需要管理员权限,有些系统还需要专门添加可写权限才能修改成功。

plaintext
# NPM registry proxy
10.x.x.x    registry.npmjs.com registry.npmjs.org
# NPM registry proxy
10.x.x.x    registry.npmjs.com registry.npmjs.org

结语

通过以上配置,我们就可以使用自己的服务器来代理npm包的下载,目前我已经使用了一段时间,下载速度明显提升,推荐有这个问题且不方便用梯子的也尝试一下。

本文部分内容由Cursor(AI驱动的代码编辑器)协助编写和润色。