从零开始的VPS搭建日记:Docker部署与Nginx SNI分流Xray五合一配置

从零开始的VPS搭建日记:Docker部署与Nginx SNI分流Xray五合一配置

在开始折腾VPS前可以先重装一个干净的系统,root用户一键重装VPS脚本(重装前一定要先备份好重要数据!!!):

curl -O https://raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh || wget -O ${_##*/} $_

比如重装成debian13

bash reinstall.sh debian 13

重装过程会让输入密码,请记好,等跑完命令后重启:

reboot

用root+刚才设置的密码登录,ssh默认端口22,添加用户名为 admin 的普通用户

useradd -m -s /bin/bash admin

授予admin用户sudo权限

tee /etc/sudoers.d/admin <<< 'admin ALL=(ALL) NOPASSWD: ALL' && chmod 440 /etc/sudoers.d/admin

在admin用户下配置SSH密钥登录一键脚本(前提是已经在GitHub中上传了公钥,并且在电脑本地已经保存有对应的私钥,其中xxx为你的GitHub用户名,-p 2222 修改ssh端口为2222,-d 禁止密码登录)

bash <(curl -fsSL git.io/key.sh) -og xxx -p 2222 -d

此时可以新开一个shell用admin+你的私钥验证是否可以成功登录,登录成功后就可以愉快的开始你的折腾之旅啦

安装docker

curl -fsSL https://get.docker.com | sudo sh

把当前用户admin加入docker组

sudo usermod -aG docker $USER
newgrp docker

创建文件夹

sudo mkdir -p /opt/jellyfin /opt/aria2 /opt/ghost /opt/portainer /srv/media

安装jellyfin并启动:

sudo tee /opt/jellyfin/docker-compose.yml<< 'EOF'
services:
  jellyfin:
    image: jellyfin/jellyfin
    container_name: jellyfin
    restart: unless-stopped
    ports:
      - "8096:8096/tcp"
      - "7359:7359/udp"
    volumes:
      - /opt/jellyfin/config:/config
      - /opt/jellyfin/cache:/cache
      - type: bind
        source: /srv/media
        target: /media
EOF
docker compose -f /opt/jellyfin/docker-compose.yml up -d

安装aria2并启动:

cd /opt/aria2 && sudo nano docker-compose.yml
services:
  # --- WARP 网络核心 (负责拨号和端口转发) ---
  warp-wg:
    image: lscr.io/linuxserver/wireguard:latest
    container_name: warp-wg
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    environment:
      - PUID=0
      - PGID=0
      - TZ=Asia/Shanghai
    volumes:
      # ⚠️ 请确保在该目录下放好你生成的 wg0.conf
      - ./wg-config:/config
    ports:
      # 原本 host 模式不需要映射,现在因为有了隔离,必须在这里手动映射
      - "6800:6800"             # RPC 端口
      - "6888:6888/tcp"         # BT 监听端口 (匹配你截图的端口)
      - "6888:6888/udp"         # DHT 监听端口 (匹配你截图的端口)
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
    restart: unless-stopped

  # --- Aria2 下载器 (流量强制走上面的容器) ---
  aria2:
    image: p3terx/aria2-pro
    container_name: aria2
    # 🌟 核心修改:取消 network_mode: host,改为借用 warp-wg 的网络
    network_mode: "service:warp-wg"
    restart: unless-stopped
    logging:
      options:
        max-size: "1m"
    environment:
      - PUID=0
      - PGID=0
      - RPC_SECRET=your_password    # 保持你的原密码
      - SPECIAL_MODE=move       # 保持你的特殊模式
    volumes:
      # 保持你原有的宿主机路径映射
      - /opt/aria2/config:/config
      - /opt/aria2/downloads:/downloads
      - /srv/media:/completed
    depends_on:
      - warp-wg

docker compose up -d

aria2配置要修改一下dest-dir以实现下载的媒体文件自动移动到/srv/media中,一键修改:

sudo sed -i 's|dest-dir=/downloads/completed|dest-dir=/completed|' /opt/aria2/config/script.conf
安装nginx,可以直接apt install nginx安装,也可以用官方源安装(推荐,因为安装的版本比较新,功能更齐全)
sudo apt install curl gnupg2 ca-certificates lsb-release debian-archive-keyring
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
    | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
# 创建 .gnupg 目录并设置正确权限(GPG 对权限要求严格)
mkdir -p ~/.gnupg
chmod 700 ~/.gnupg

# 再次执行你的命令
gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg

输出如截图再继续往下

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
https://nginx.org/packages/debian `lsb_release -cs` nginx" \
    | sudo tee /etc/apt/sources.list.d/nginx.list
echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
    | sudo tee /etc/apt/preferences.d/99nginx
sudo apt update
sudo apt install nginx

下载AriaNg并解压到/usr/share/nginx/html

curl -L $(curl -s https://api.github.com/repos/mayswind/AriaNg/releases/latest \
  | grep "browser_download_url.*AllInOne" \
  | cut -d '"' -f 4) -o AriaNg-AllInOne.zip \
  && sudo unzip AriaNg-AllInOne.zip -d /usr/share/nginx/html

root用户下载安装acme.sh并设置默认ca服务器为Letsencrypt

curl https://get.acme.sh | sh 
. .bashrc  
acme.sh --upgrade --auto-upgrade  
acme.sh --set-default-ca --server letsencrypt 

导入CF_Token,在Cloudflare>profile>API Token中创建

export CF_Token="xxx"  

申请域名证书并安装到/etc/nginx/ssl文件夹

mkdir -p /etc/nginx/ssl && chmod 700 /etc/nginx/ssl
acme.sh --issue --dns dns_cf -d yourdomain.com --keylength ec-256 --force
acme.sh --install-cert -d yourdomain.com --ecc --fullchain-file /etc/nginx/ssl/xxx.crt --key-file /etc/nginx/ssl/xxx.key --reloadcmd "nginx -s reload"

修改nginx配置:

sudo nano /etc/nginx/nginx.conf
worker_processes     auto;
worker_rlimit_nofile 65535;
error_log            /var/log/nginx/error.log notice;
pid                  /var/run/nginx.pid;

events {
    worker_connections 4096;
    multi_accept       on;
}

# ─── Stream (SNI 分流) ──────────────────────────────────────────────────────
stream {
    map $ssl_preread_server_name $backend {
        web1.com unix:/dev/shm/vless.sock;
        web2.com unix:/dev/shm/cdn.sock;
        web3.com unix:/dev/shm/docker.sock;
        web4.com unix:/dev/shm/ghost.sock;
    }

    server {
        listen         443;
        ssl_preread    on;
        proxy_protocol on;
        proxy_pass     $backend;
    }
}

# ─── HTTP ───────────────────────────────────────────────────────────────────
http {
    include      mime.types;
    default_type application/octet-stream;

    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    map $request $secretfilter {
        ~*^(?<p>.*[\?&]api_key=)[^&]*(?<s>.*)$  "${p}***$s";
        ~*^(?<p>.*[\?&]ApiKey=)[^&]*(?<s>.*)$   "${p}***$s";
        default                                  $request;
    }

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    log_format stripsecrets '$remote_addr $host - $remote_user [$time_local] '
                    '"$secretfilter" $status $body_bytes_sent '
                    '$request_length $request_time $upstream_response_time '
                    '"$http_referer" "$http_user_agent"';

    access_log /var/log/nginx/access.log main buffer=32k flush=5m;

    # ── 基础性能 ───────────────────────────────────────────────────────────
    sendfile          on;
    tcp_nopush        on;
    tcp_nodelay       on;
    keepalive_timeout 65;
    server_tokens     off;

    # ── 全局代理默认值(各 server/location 内无需重复声明)────────────────
    proxy_http_version        1.1;
    proxy_socket_keepalive    on;
    proxy_headers_hash_max_size    1024;    
    proxy_headers_hash_bucket_size 128;   

    # ── TLS ───────────────────────────────────────────────────────────────
    ssl_session_cache         shared:SSL:10m;
    ssl_session_timeout       1d;
    ssl_session_tickets       off;
    ssl_protocols             TLSv1.2 TLSv1.3;
    ssl_ciphers               ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers on;   # 仅对 TLS 1.2 生效,TLS 1.3 由客户端决定

    # ── Gzip ──────────────────────────────────────────────────────────────
    gzip            on;
    gzip_vary       on;
    gzip_proxied    any;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_types      text/plain text/css text/xml
                    application/json application/javascript
                    application/rss+xml application/atom+xml
                    image/svg+xml;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;

    # ══════════════════════════════════════════════════════════════════════════════
# Web1 — VLESS /Openclaw Aria2
# ══════════════════════════════════════════════════════════════════════════════
server {
    # reality.sock = Reality 伪装回源,由 Xray realitySettings.target 转入,非 stream 直接转发
    listen unix:/dev/shm/reality.sock ssl proxy_protocol;
    http2 on;
    set_real_ip_from unix:;
    real_ip_header proxy_protocol;

    ssl_certificate     /etc/nginx/ssl/web1.crt;
    ssl_certificate_key /etc/nginx/ssl/web1.key;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    location / {
        proxy_pass http://127.0.0.1:18789;
        include    snippets/proxy-headers.conf;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }

    location ^~ /aria2 {
        alias     /usr/share/nginx/html;
        index     index.html;
        autoindex off;
        expires   30d;
    }

    location /jsonrpc {
        proxy_pass     http://localhost:6800/jsonrpc;
        proxy_redirect off;
        include        snippets/proxy-headers.conf;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

# ══════════════════════════════════════════════════════════════════════════════
# Web2 — Jellyfin (CDN 域)
# ══════════════════════════════════════════════════════════════════════════════
server {
    listen unix:/dev/shm/cdn.sock ssl proxy_protocol;
    http2 on;
    set_real_ip_from unix:;
    real_ip_header       proxy_protocol;
    client_max_body_size 20M;

    ssl_certificate     /etc/nginx/ssl/web2.crt;
    ssl_certificate_key /etc/nginx/ssl/web2.key;
    access_log /var/log/nginx/access.log stripsecrets;
    
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options    "nosniff" always;
    add_header Permissions-Policy        "accelerometer=(), ambient-light-sensor=(), battery=(), bluetooth=(), camera=(), clipboard-read=(), display-capture=(), document-domain=(), encrypted-media=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), keyboard-map=(), local-fonts=(), magnetometer=(), microphone=(), payment=(), publickey-credentials-get=(), serial=(), sync-xhr=(), usb=(), xr-spatial-tracking=()" always;
    add_header Content-Security-Policy   "default-src https: data: blob: ; img-src 'self' https://* ; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com https://www.youtube.com blob:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; font-src 'self'" always;

    location /xhttp_path {
        grpc_pass grpc://unix:/dev/shm/xhttp.sock;
        grpc_set_header Host            $host;
        grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location / {
        proxy_pass      http://127.0.0.1:8096;
        proxy_buffering off;
        include         snippets/proxy-headers.conf;
        proxy_set_header X-Forwarded-Protocol $scheme;
        proxy_set_header X-Forwarded-Host     $http_host;
    }

    location /socket {
        proxy_pass http://127.0.0.1:8096;
        include    snippets/proxy-headers.conf;
        proxy_set_header X-Forwarded-Protocol $scheme;
        proxy_set_header X-Forwarded-Host     $http_host;
        proxy_set_header Upgrade              $http_upgrade;
        proxy_set_header Connection           $connection_upgrade;
    }
}

# ══════════════════════════════════════════════════════════════════════════════
# Web3 — Docker / Portainer
# ══════════════════════════════════════════════════════════════════════════════
server {
    listen unix:/dev/shm/docker.sock ssl proxy_protocol;
    http2 on;
    set_real_ip_from unix:;
    real_ip_header      proxy_protocol;

    ssl_certificate     /etc/nginx/ssl/web3.crt;
    ssl_certificate_key /etc/nginx/ssl/web3.key;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    location / {
        proxy_pass https://127.0.0.1:9443;
        include    snippets/proxy-headers.conf;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

# ══════════════════════════════════════════════════════════════════════════════
# Web4 — Ghost Blog
# ══════════════════════════════════════════════════════════════════════════════
server {
    listen unix:/dev/shm/ghost.sock ssl proxy_protocol;
    http2 on;
    set_real_ip_from unix:;
    real_ip_header proxy_protocol;

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

    client_max_body_size 50M;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    location / {
        proxy_pass http://127.0.0.1:2368;
        include    snippets/proxy-headers.conf;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
  }
}
sudo mkdir -p /etc/nginx/snippets && sudo tee /etc/nginx/snippets/proxy-headers.conf<< 'EOF'
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;
EOF

安装xray

sudo bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

在修改xray配置前先执行以下命令获取必要的uuid、reality和cloudflare warp的私钥和公钥等信息

xray uuid
xray uuid
xray uuid
xray x25519
bash -c "$(curl -L warp-reg.vercel.app)"

修改配置:

sudo nano /usr/local/etc/xray/config.json
{
  "log": {
    "loglevel": "warning",
    "error": "/var/log/xray/error.log",
    "access": "/var/log/xray/access.log"
  },
  "dns": {
      "servers": ["https+local://1.1.1.1/dns-query","localhost"]
    },
  "policy": {
    "levels": {
      "0": {
        "handshake": 7,
        "connIdle": 420
            }
           }
          },
  "inbounds": [
    {
      "listen": "/dev/shm/vless.sock,0666",
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "",  //uuid1
            "flow": "xtls-rprx-vision"
          }
        ],
        "decryption": "none",
        "fallbacks": [
          {
            "dest": "/dev/shm/xhttp.sock"
          }
        ]
      },
      "streamSettings": {
        "network": "raw",
        "security": "reality",
        "realitySettings": {
          "target": "/dev/shm/reality.sock",
          "xver": 1,
          "serverNames": [
            "web1.com"
          ],
          "privateKey": "",  //x25519私钥
          "shortIds": [""]
        },
        "rawSettings": {
          "acceptProxyProtocol": true
        }
      },
      "sniffing": {
        "enabled": true,
        "destOverride": [
          "http",
          "tls",
          "quic"
        ]
      }
    },
    {
      "listen": "/dev/shm/xhttp.sock,0666",
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": ""    //uuid2
          },
          {
            "id": "",   //uuid3
            "email": "Reserved"
           }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "xhttp",
        "xhttpSettings": {
          "path": "/xhttp_path"  //路径
        }
      },
      "sniffing": {
        "enabled": true,
        "destOverride": [
          "http",
          "tls",
          "quic"
        ]
      }
    }
  ],
  "outbounds": [
    {
      "tag":"direct",
      "protocol": "freedom",
      "settings": {
        "domainStrategy": "UseIPv4"
    }
    },
    {
       "tag": "block",
       "protocol": "blackhole"
     },
  {
  "tag": "warp",
  "protocol": "wireguard",
  "settings": {
    "secretKey": "",  //私钥
    "address": [
      "172.16.0.2/32",
      "/128"  //IPV6地址
    ],
    "peers": [
      {
        "publicKey": "",   //公钥
        "endpoint":  "162.159.192.4:2408"  //endpoint地址
      }
    ],
  "mtu": 1420,
  "reserved": [35, 176, 72]
   }
  }
 ],
    "routing": {
        "domainStrategy": "IPIfNonMatch",
        "rules": [
            {
                "type": "field",
                "ip": ["geoip:private"],
                "outboundTag": "block"
            },
            {
                "type": "field",
                "domain": ["geosite:category-ads-all"],
                "outboundTag": "block"
            },
            {
                "type": "field",
                "domain": ["geosite:cn"],
                "outboundTag": "warp"
            },
            {
                "type": "field",
                "ip": ["geoip:cn"],
                "outboundTag": "warp"
            },
            {
                "type": "field",
                "protocol": ["bittorrent"],
                "outboundTag": "block"
            },
            {
                "type": "field",
                "user": ["Reserved"],
                "outboundTag": "warp"
            }
      ]
   }
}

安装hysteria2并修改配置

HYSTERIA_USER=root bash <(curl -fsSL https://get.hy2.sh/) && sudo nano /etc/hysteria/config.yaml
listen: :443
tls:
  cert: /etc/nginx/ssl/web3.crt  #web3对应的证书和密钥
  key: /etc/nginx/ssl/web3.key

auth:
  type: password
  password: your_strong_password #修改密码

masquerade:
  type: proxy
  proxy:
    url: https://127.0.0.1:9443 #portainer监听的端口
    rewriteHost: true

congestion:
  type: bbr
  bbrProfile: standard

quic:
  initStreamReceiveWindow: 26843545
  maxStreamReceiveWindow: 26843545
  initConnReceiveWindow: 67108864
  maxConnReceiveWindow: 67108864

acl:
  inline:
    - reject(geoip:cn)
    - reject(geosite:cn)
    - direct(all)
  geoip: /usr/local/share/xray/geoip.dat
  geosite: /usr/local/share/xray/geosite.dat

普通用户admin中安装openclaw

curl -fsSL https://openclaw.ai/install.sh | bash

修改配置~/.openclaw/openclaw.json,实现openclaw网页访问,其中代码片段gateway如下:

"gateway": {
    "mode": "local",
    "bind": "lan",
    "auth": {
      "mode": "token",
      "token": "your openclaw gateway token" //默认自动生成token,也可以自行修改
    },
    "port": 18789,
    "tailscale": {
      "mode": "off",
      "resetOnExit": false
    },
    "controlUi": {
      "allowedOrigins": [
         "http://localhost:18789",
         "http://127.0.0.1:18789",
         "https://web1.com" //修改为自己的域名,对应nginx中的web1
    ],
      "allowInsecureAuth": false
    },
    "nodes": {
      "denyCommands": [
        "camera.snap",
        "camera.clip",
        "screen.record",
        "contacts.add",
        "calendar.add",
        "reminders.add",
        "sms.send",
        "sms.search"
      ]
    }
  }
openclaw gateway restart
openclaw gataway status 

安装portainer并启动

cd /opt/portainer && sudo nano docker-compose.yml
services:
  portainer:
    container_name: portainer
    image: portainer/portainer-ce:sts
    restart: always
    command:
      - --sslcert=/certs/web3.crt  #nginx中web3对应的证书和密钥
      - --sslkey=/certs/web3.key
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
      - /etc/nginx/ssl:/certs:ro 
    ports:
      - 9443:9443
      - 8000:8000
volumes:
  portainer_data:
    name: portainer_data
networks:
  default:
    name: portainer_network
docker compose up -d

安装ghost,安装前需要先在resend里绑定博客域名并创建API key用于后续发送邮件验证:

cd /opt/ghost && sudo tee docker-compose.yml<< 'EOF'
services:
  ghost:
    image: ghost:5-alpine
    container_name: ghost
    restart: unless-stopped
    ports:
      - "127.0.0.1:2368:2368"
    environment:
      url: ${GHOST_URL}
      database__client: mysql
      database__connection__host: db
      database__connection__user: ghost
      database__connection__password: ${GHOST_DB_PASSWORD}
      database__connection__database: ghost
      TZ: Asia/Shanghai
      mail__transport: SMTP
      mail__options__host: smtp.resend.com
      mail__options__port: 465
      mail__options__secure: "true"
      mail__options__auth__user: resend
      mail__options__auth__pass: ${RESEND_API_KEY}
      mail__from: "[email protected]"  # 必须是在 Resend 验证过的域名
    volumes:
      - ghost_content:/var/lib/ghost/content
    depends_on:
      db:
        condition: service_healthy
    networks:
      - ghost_network

  db:
    image: mysql:8.0
    container_name: ghost_db
    restart: unless-stopped
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ghost
      MYSQL_USER: ghost
      MYSQL_PASSWORD: ${GHOST_DB_PASSWORD}
      TZ: Asia/Shanghai
    volumes:
      - ghost_db:/var/lib/mysql
    networks:
      - ghost_network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost",
             "-u", "ghost", "-p${GHOST_DB_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

volumes:
  ghost_content:
  ghost_db:

networks:
  ghost_network:
    driver: bridge
EOF
sudo nano .env
MYSQL_ROOT_PASSWORD=your_password
GHOST_DB_PASSWORD=your_password
#替换为自己网站域名
GHOST_URL=https://web4.com
#Resend API key
RESEND_API_KEY=xxx
docker compose up -d

启动nginx、xray、hysteria2并设置开机启动

sudo nginx -t && sudo systemctl restart nginx
sudo systemctl enable xray hysteria-server  --now

设置一个定时任务,每天自动更新xray的geoip和geosite,root用户创建脚本:

mkdir -p /root/script && tee /root/script/update_geodata.sh<< 'EOF'
#!/usr/bin/env bash
# shellcheck disable=SC2268

# This Bash script to install the latest release of geoip.dat and geosite.dat:

# https://github.com/v2fly/geoip
# https://github.com/v2fly/domain-list-community

# Depends on cURL, please solve it yourself

# You may plan to execute this Bash script regularly:

# install -m 755 install-dat-release.sh /usr/local/bin/install-dat-release

# 0 0 * * * /usr/local/bin/install-dat-release > /dev/null 2>&1

# You can set this variable whatever you want in shell session right before running this script by issuing:
# export DAT_PATH='/usr/local/lib/v2ray'
DAT_PATH=${DAT_PATH:-/usr/local/share/xray}

DOWNLOAD_LINK_GEOIP="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
DOWNLOAD_LINK_GEOSITE="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
file_ip='geoip.dat'
file_dlc='geosite.dat'
dir_tmp="$(mktemp -d)"

curl() {
  $(type -P curl) -L -q --retry 5 --retry-delay 10 --retry-max-time 60 "$@"
}

check_if_running_as_root() {
  # If you want to run as another user, please modify $UID to be owned by this user
  if [[ "$UID" -ne '0' ]]; then
    echo "WARNING: The user currently executing this script is not root. You may encounter the insufficient privilege error."
    read -r -p "Are you sure you want to continue? [y/n] " cont_without_been_root
    if [[ x"${cont_without_been_root:0:1}" = x'y' ]]; then
      echo "Continuing the installation with current user..."
    else
      echo "Not running with root, exiting..."
      exit 1
    fi
  fi
}

download_files() {
  if ! curl -R -H 'Cache-Control: no-cache' -o "${dir_tmp}/${2}" "${1}"; then
    echo 'error: Download failed! Please check your network or try again.'
    exit 1
  fi
  if ! curl -R -H 'Cache-Control: no-cache' -o "${dir_tmp}/${2}.sha256sum" "${1}.sha256sum"; then
    echo 'error: Download failed! Please check your network or try again.'
    exit 1
  fi
}

check_sum() {
  (
    cd "${dir_tmp}" || exit
    for i in "${dir_tmp}"/*.sha256sum; do
      if ! sha256sum -c "${i}"; then
        echo 'error: Check failed! Please check your network or try again.'
        exit 1
      fi
    done
  )
}

install_file() {
  install -m 644 "${dir_tmp}"/${file_dlc} "${DAT_PATH}"/${file_dlc}
  install -m 644 "${dir_tmp}"/${file_ip} "${DAT_PATH}"/${file_ip}
  rm -r "${dir_tmp}"
}

main() {
  check_if_running_as_root
  download_files $DOWNLOAD_LINK_GEOIP $file_ip
  download_files $DOWNLOAD_LINK_GEOSITE $file_dlc
  check_sum
  install_file
}

main "$@"

systemctl restart xray
systemctl restart hysteria-server
EOF
chmod +x /root/script/update_geodata.sh

每天早上7点更新,并且重启xray和hysteria2

crontab -e
0 7 * * * bash /root/script/update_geodata.sh

开启bbr拥塞控制算法、TCP Fast Open和提高TCP全连接队列最大长度并验证是否生效

sudo tee /etc/sysctl.d/99-bbr.conf << 'EOF'
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
net.ipv4.tcp_fastopen=3
net.core.somaxconn=32768
EOF
sudo sysctl --system  
sudo sysctl net.ipv4.tcp_congestion_control 
sudo sysctl net.ipv4.tcp_fastopen
sudo sysctl net.core.somaxconn

安装ufw、fail2ban

sudo apt install ufw fail2ban

ufw默认拒绝所有入站,放行所有出站,开放tcp80/443、ssh自定义端口2222和udp443端口

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
#放行ssh,如果没自定义ssh端口,请改回22,重要!!!否则将与vps失联!!!
sudo ufw allow 2222/tcp && sudo ufw enable
#查看ufw状态
sudo ufw status verbose

创建fail2ban配置并启动、查看状态

sudo tee /etc/fail2ban/jail.local<< 'EOF'
[DEFAULT]
bantime = 24h
findtime = 10m
backend = systemd
maxretry = 5
banaction = ufw

[sshd]
enabled = true
port = 2222   #修改为ssh自定义端口
maxretry = 3
bantime = 24h
EOF
sudo systemctl enable fail2ban --now && sudo fail2ban-client status sshd

针对低内存VPS,增加虚拟内存:

# 创建一个 1G 的空文件
sudo fallocate -l 1G /swapfile
# 如果上面的命令报错,请尝试这个备用命令:
# sudo dd if=/dev/zero of=/swapfile bs=1M count=1024
# 设置权限
sudo chmod 600 /swapfile
# 格式化为 Swap 格式
sudo mkswap /swapfile
# 激活 Swap 文件
sudo swapon /swapfile
# 将挂载信息写入 fstab
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# 优先使用物理内存
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.d/99-bbr.conf && sudo sysctl --system

至此在服务器上
1、实现了nginx前置监听TCP443端口,sni分流,其中
①web1.com为openclaw网页,web1.com/aria2为AriaNg网页
②web2.com为jellyfin网页,媒体播放
③web3.com为portainer网页,docker管理
④web4.com为ghost网页,博客网站

2、xray五合一配置,与参考链接实现方式不一样,参考链接是xray前置监听443,本案例是nginx前置监听443,但是实现的效果是一样的。
①XTLS(Vision)+Reality 直连
②xhttp+Reality 直连
③上行 xhttp+TLS+CDN | 下行 xhttp+Reality (上下行不同sni)
④xhttp+TLS 过CDN
⑤上行 xhttp+Reality | 下行 xhttp+TLS+CDN(上下行不同sni)
其中,安全协议为Reality的伪装网站(sni)是1.①即openclaw网页,安全协议为TLS的伪装网站(sni)是1.②即jellyfin网页

3、hysteria2监听UDP443端口,与nginx监听的TCP443不冲突,hysteria2的伪装网站为1.③即portainer网页

4、安装了openclaw,可以和龙虾在网页web1.com上对话,指挥它干活,至于怎么和聊天应用比如telegram对接,请自行google。

5、安装了jellyfin,通过web2.com访问,实现媒体播放。

6、安装了aria2,通过web1.com/aria2访问,通过web1.com:443连接后端aria2 RPC接口,如截图所示

下载完成后会自动把文件移动到服务器的/srv/media这个文件夹,在AriaNg里指定下载路径如/downloads/tv,就会下载到/srv/media/tv文件夹,以此类推,可以对下载的文件类型进行分类,jellyfin会读取/srv/media里所有文件

7、安装了portainer,通过web3.com访问,docker管理页面。

8、安装了ghost博客程序,通过web4.com访问,本博客就是通过此方式实现。

9、服务器网络优化,开启了bbr拥塞控制算法;服务器安全加固,安装了ufw,fail2ban,仅对外开放2222(ssh)、80和443端口。