一些废话
啊咧?又有一个家伙忘记续费 VPS 导致数据被删博客失联!
原因竟然是重装电脑系统但是没安装 Outlook 导致续费邮件发了一堆还长达半个月没被看见,最后还是群友提醒域名 SSL 认证失败访问不了博客才发现事情的严重性……
那么来着手重装 VPS 系统以及 Web 环境吧!
重装系统
由于 RackNerd VPS 提供安装的系统版本实在是太老旧,一番寻找发现有个很便捷项目:一键DD/重装脚本 (One-click reinstall OS on VPS)
以前在 VPS 上使用的系统都是 Ubuntu ,但最少有一个受不了的状况让我决定换掉:SSH 终端有订阅提醒以及广告……
在 Fedora 与 AlmaLinux 之间纠结了一阵,最后还是选择了天然带 LTS 属性的后者。
下载脚本:
curl -O https://raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh || wget -O ${_##*/} $_
执行重装:
bash reinstall.sh almalinux --password PASSWORD --ssh-port PORT
直接设定新系统的 Root 密码与 SSH 端口,又节省大量细节操作,等两三分钟即重装完成且重启到新系统上面了。
新主机名:
hostnamectl set-hostname HOSTNAME
登录 SSH 发现主机名很长很奇怪,结果是临时的,那么直接设定一个固定的。
环境搭建
以前用的 Web 环境一键搭建脚本一直都是 OneinStack ,但早有听闻被收购恶意挂马投毒,所以也是不敢用了;我也不喜欢用面板,太商业化(基本收费),还是最小化手搓吧!
添加 Docker 仓库:
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
清理缓存及更新组件:
dnf clean all && dnf upgrade -y
安装 Docker 组件:
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
启动 Docker 服务:
systemctl enable --now docker
现在,Docker 已被启动和设为开机自启。
安装终端复用工具:
dnf install -y tmux
创建新的会话:
tmux new -s web
连接已有会话:
tmux attach -t web
这样就不怕 SSH 掉线重连后没有记录了。
现在我们来准备一个工作目录,执行:
mkdir -p /data/web/{caddy,php} && cd /data/web
预期目录结构:
/data/web/
├── .env # 环境变量文件
├── compose.yaml # Compose 配置文件
├── caddy/
│ └── Caddyfile # Caddy 配置文件
├── certs/ # SSL 配置持久化目录
├── import/ # SQL 文件初始化目录
├── mariadb/ # MariaDB 数据持久化目录
├── php/
│ └── Dockerfile # 构建自定义 PHP 镜像的文件
├── sites/ # 网站根目录 (多站点存放处)
│ ├── example.com/ # 站点 A 源码
│ ├── test.com/ # 站点 B 源码
│ └── ... # 更多站点
└── logs/ # 日志统一存放目录
├── caddy/ # 访问日志
├── mariadb/ # 数据库日志
├── php-fpm/ # PHP 日志
└── redis/ # Redis 日志
准备以下 4 个文件并放置在对应位置,其中 Caddyfile 仅供参考,其它文件均可开箱即用。
.env
# -----------------------------------------------------------
# 服务配置
# -----------------------------------------------------------
# 设置默认时区
TZ=Asia/Shanghai
# MariaDB 密码
MYSQL_ROOT_PASSWORD=mariadb
# -----------------------------------------------------------
# 镜像版本
# -----------------------------------------------------------
# Caddy
CADDY_VERSION=2.10.2
# Php
PHP_VERSION=8.5
# MariaDB
MARIADB_VERSION=11.8
# Redis
REDIS_VERSION=8.4
compose.yaml
# ===========================================================
# 🧩 Multi-Site PHP Stack
# -----------------------------------------------------------
# - 🌐 Caddy
# - ⚙️ Php-fpm
# - 🗄️ MariaDB
# - ⚡ Redis
# -----------------------------------------------------------
# Maintainer: galnetwen
# Updated: 2026-01-04
# ===========================================================
services:
# ---------------------------------------------------------
# - Caddy
# - 自动 HTTPS + 极简配置
# ---------------------------------------------------------
caddy:
image: caddy:${CADDY_VERSION}
container_name: caddy
restart: unless-stopped
cap_add:
- NET_ADMIN
environment:
TZ: ${TZ:-Asia/Shanghai}
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./logs/caddy:/var/log/caddy
- ./caddy:/etc/caddy
- ./certs:/data
- ./sites:/srv/sites
networks:
- webnet
# ---------------------------------------------------------
# - Php-fpm
# - 自定义 PHP 运行环境
# ---------------------------------------------------------
php-fpm:
build:
context: ./php
args:
PHP_VERSION: ${PHP_VERSION}
image: galnetwen/php-fpm:${PHP_VERSION}
container_name: php-fpm
restart: unless-stopped
environment:
TZ: ${TZ:-Asia/Shanghai}
volumes:
- ./logs/php-fpm:/var/log/php-fpm
- ./sites:/var/www/html
networks:
- webnet
# ---------------------------------------------------------
# - MariaDB
# - 数据库管理系统
# ---------------------------------------------------------
mariadb:
image: mariadb:${MARIADB_VERSION}
container_name: mariadb
restart: always
environment:
TZ: ${TZ:-Asia/Shanghai}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
- ./logs/mariadb:/var/log/mysql
- ./import:/docker-entrypoint-initdb.d
- ./mariadb:/var/lib/mysql
networks:
- webnet
# ---------------------------------------------------------
# - Redis
# - 缓存与会话存储
# ---------------------------------------------------------
redis:
image: redis:${REDIS_VERSION}
container_name: redis
restart: always
environment:
TZ: ${TZ:-Asia/Shanghai}
volumes:
- ./logs/redis:/var/log/redis
networks:
- webnet
# -----------------------------------------------------------
# - Networks
# - 统一桥接网络名称供所有服务通信
# -----------------------------------------------------------
networks:
webnet:
driver: bridge
name: webnet
Caddyfile
如果你想在调试时临时不用 HTTPS ,可以给域名末尾加上 :80 端口号,这样就不会自动申请证书及启用 HTTPS 。
# -----------------------------------------------------------
# 全局配置
# -----------------------------------------------------------
{
# 证书关联邮箱
email [email protected]
# Cloudflare IP
servers {
trusted_proxies static 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32
}
}
# -----------------------------------------------------------
# 服务配置
# -----------------------------------------------------------
(main) {
root * /srv/sites/{args[0]}
encode zstd gzip
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
-Via
-Server
-X-Powered-By
}
@static path *.ico *.jpg *.jpeg *.png *.gif *.webp *.svg *.css *.js *.otf *.woff *.woff2 *.mp4
header @static Cache-Control "public, max-age=31536000"
php_fastcgi php-fpm:9000 {
root /var/www/html/{args[0]}
env CF-Connecting-IP {http.request.header.CF-Connecting-IP}
capture_stderr
}
file_server
log {
output file /var/log/caddy/{args[0]}.log {
roll_size 100MB
roll_keep 4
}
}
}
# -----------------------------------------------------------
# 非法域名
# -----------------------------------------------------------
:80, :443 {
# 直接关闭连接
abort
# 返回 403 错误
# respond "Access Denied" 403
}
# -----------------------------------------------------------
# 站点配置
# -----------------------------------------------------------
haremu.com {
@unallowed path /wp-config.php /xmlrpc.php /readme.html /license.txt
respond @unallowed "Access Denied" 403
@forbidden {
path_regexp hidden ^/\.
not path /.well-known/*
}
respond @forbidden "Access Denied" 403
import main haremu.com
}
acg.sx {
route {
@folder path_regexp f ^/images/([^/]+)$
rewrite @folder /images.php?folder={re.f.1}&{query}
@textures path_regexp t ^/textures/([^/]+)$
rewrite @textures /images.php?textures={re.t.1}&{query}
rewrite /images /images.php?{query}
@index path /
redir @index /images 302
}
import main acg.sx
}
Dockerfile
直接取自 WordPress 官方,根本不用费脑子和担心编译失败,还会清理用完的编译工具来给镜像缩减体积。
# ===========================================================
# 🧩 Php-fpm for WordPress
# -----------------------------------------------------------
# - Base: Debian + Php-fpm
# - Extensions: mysqli, pdo_mysql, gd, zip, intl, exif, bcmath, opcache, redis
# -----------------------------------------------------------
# Source: https://github.com/docker-library/wordpress/blob/master/latest/php8.5/fpm/Dockerfile
# Updated: 2026-01-04
# ===========================================================
ARG PHP_VERSION
FROM php:${PHP_VERSION:-8.5}-fpm
LABEL maintainer="galnetwen" \
description="Custom Php-fpm for WordPress based on Debian" \
version="1.0"
# persistent dependencies
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
# Ghostscript is required for rendering PDF previews
ghostscript \
; \
rm -rf /var/lib/apt/lists/*
# install the PHP extensions we need (https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions)
RUN set -ex; \
\
savedAptMark="$(apt-mark showmanual)"; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
libavif-dev \
libfreetype6-dev \
libicu-dev \
libjpeg-dev \
libmagickwand-dev \
libpng-dev \
libwebp-dev \
libzip-dev \
; \
\
docker-php-ext-configure gd \
--with-avif \
--with-freetype \
--with-jpeg \
--with-webp \
; \
docker-php-ext-install -j "$(nproc)" \
bcmath \
exif \
gd \
intl \
mysqli \
zip \
pdo_mysql \
; \
# https://pecl.php.net/package/imagick
pecl install imagick-3.8.1; \
pecl install redis; \
docker-php-ext-enable imagick redis; \
rm -r /tmp/pear; \
\
# some misbehaving extensions end up outputting to stdout 🙈 (https://github.com/docker-library/wordpress/issues/669#issuecomment-993945967)
out="$(php -r 'exit(0);')"; \
[ -z "$out" ]; \
err="$(php -r 'exit(0);' 3>&1 1>&2 2>&3)"; \
[ -z "$err" ]; \
\
extDir="$(php -r 'echo ini_get("extension_dir");')"; \
[ -d "$extDir" ]; \
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
apt-mark auto '.*' > /dev/null; \
apt-mark manual $savedAptMark; \
ldd "$extDir"/*.so \
| awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); printf "*%s\n", so }' \
| sort -u \
| xargs -r dpkg-query --search \
| cut -d: -f1 \
| sort -u \
| xargs -rt apt-mark manual; \
\
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
rm -rf /var/lib/apt/lists/*; \
\
! { ldd "$extDir"/*.so | grep 'not found'; }; \
# check for output like "PHP Warning: PHP Startup: Unable to load dynamic library 'foo' (tried: ...)
err="$(php --version 3>&1 1>&2 2>&3)"; \
[ -z "$err" ]
# set recommended PHP.ini settings
# see https://secure.php.net/manual/en/opcache.installation.php
# enabled by default in the php images: https://github.com/docker-library/php/pull/1587
RUN set -eux; \
{ \
echo 'opcache.memory_consumption = 256'; \
echo 'opcache.interned_strings_buffer = 16'; \
echo 'opcache.max_accelerated_files = 20000'; \
echo 'opcache.revalidate_freq = 2'; \
} > "$PHP_INI_DIR/conf.d/opcache-recommended.ini"
RUN set -eux; \
{ \
echo 'memory_limit = 512M'; \
echo 'post_max_size = 64M'; \
echo 'upload_max_filesize = 64M'; \
} > "$PHP_INI_DIR/conf.d/wordpress-optimised.ini"
# https://wordpress.org/support/article/editing-wp-config-php/#configure-error-logging
RUN set -eux; \
{ \
# https://www.php.net/manual/en/errorfunc.constants.php
# https://github.com/docker-library/wordpress/issues/420#issuecomment-517839670
echo 'error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECOVERABLE_ERROR'; \
echo 'display_errors = Off'; \
echo 'display_startup_errors = Off'; \
echo 'log_errors = On'; \
echo 'error_log = /dev/stderr'; \
echo 'log_errors_max_len = 1024'; \
echo 'ignore_repeated_errors = On'; \
echo 'ignore_repeated_source = Off'; \
echo 'html_errors = Off'; \
} > "$PHP_INI_DIR/conf.d/error-logging.ini"
VOLUME /var/www/html
CMD ["php-fpm"]
如果这些文件都准备完毕、修改妥当,那现在可以先把 PHP 给编译了先:
docker compose build
编译完毕没有报错出现,即可上线所有服务测试了!
docker compose up -d
对于 sites 目录的所有者,在上传完网站源码后,需要修改所有者以确保正常运作:
chown -R 33:33 /data/web/sites



叨叨几句... NOTHING