概述
本文介绍如何在一台全新的 Linux 服务器(Ubuntu、Debian 或 CentOS)上,通过一个 Shell 脚本实现“一键部署网站环境”。
该脚本能够自动完成以下任务:
交互式输入网站目录与域名
安装 Nginx、PHP-FPM 及常用 PHP 扩展
使用 Docker 安装 MySQL 并创建数据库与用户
自动生成 Nginx 配置文件(支持 WordPress 伪静态)
一键申请并部署 Let’s Encrypt 免费 SSL 证书
实现 HTTP 到 HTTPS 的自动跳转与安全配置
输出数据库、SSL、Nginx 等关键信息,方便后续维护
整个部署过程无需复杂操作,几分钟即可完成,适合个人站长、开发者及云服务器用户快速构建网站运行环境。
环境要求
一台公网可访问的 Linux 服务器(推荐 Ubuntu 20.04+/Debian 11+/CentOS 8+)
已正确解析的域名(A 记录指向服务器 IP)
拥有 root 或 sudo 权限
功能概览
1. 网站路径与域名设置
执行脚本时,用户需输入网站目录(如 /www/wwwroot/example.com)和域名(如 example.com)。脚本会自动创建目录结构并配置 Nginx。
2. 自动安装核心组件
脚本自动安装以下组件:
Nginx(高性能 Web 服务器)
PHP-FPM(支持 WordPress、Laravel、ThinkPHP 等框架)
Docker + MySQL(容器化数据库管理)
安装完成后自动启动并设置为开机自启。
3. MySQL 容器化部署
默认版本为 MySQL 5.7,可选择 8.0
容器名和数据目录可自定义
自动创建数据库、用户及密码
数据文件持久化保存到指定目录
执行完成后,脚本会输出 root 密码及数据库连接信息。
4. PHP 环境与扩展
脚本自动检测系统可用的 PHP 版本,并安装常用扩展,如:mysqli、curl、xml、gd、mbstring、zip。
可直接运行常见 CMS 或 PHP 框架。
5. 自动生成 Nginx 配置
配置文件路径:
/etc/nginx/sites-available/example.com.conf
并自动链接到:
/etc/nginx/sites-enabled/
主要特性包括:
WordPress 伪静态规则
PHP-FPM 文件解析
静态资源缓存策略
安全访问限制(阻止访问
.env、.git等文件)HTTP 自动跳转到 HTTPS
6. 一键申请与配置 SSL 证书
选择启用 HTTPS 后,脚本会自动:
安装 Certbot
验证域名所有权
申请 Let’s Encrypt 免费证书
自动生成 HTTPS 配置
启用 HTTP/2、OCSP、HSTS 等安全特性
证书路径:
/etc/letsencrypt/live/yourdomain/
系统会自动配置定期续期任务。
7. 自动输出配置信息
部署完成后,脚本将输出:
网站目录路径
域名信息
SSL 证书路径
Nginx 配置文件路径
MySQL 容器与数据库信息
使用步骤
-
将脚本保存为:
setup_web_stack.sh
-
赋予执行权限:
chmod +x setup_web_stack.sh
-
执行脚本:
sudo ./setup_web_stack.sh
按提示输入网站目录、域名、是否开启 HTTPS、MySQL 版本及数据库信息。
等待自动安装完成,根据输出信息进行访问与验证。
验证安装
查看服务运行状态:
sudo systemctl status nginx sudo systemctl status php*-fpm sudo docker ps
测试网站连通性:
curl -I http://example.com curl -I https://example.com
常见问题与解决
1. 出现 “ssl_stapling ignored” 警告
在 Nginx 配置中添加:
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
2. PHP 无法解析
确认配置文件中的:
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
如果不存在该文件,可改为:
fastcgi_pass 127.0.0.1:9000;
3. HTTPS 无法访问
运行:
sudo nginx -t && sudo systemctl reload nginx
确认证书路径正确无误。
4. MySQL 无法连接
确认容器已启动:
sudo docker ps
若连接失败,请使用:
mysql -h127.0.0.1 -uroot -p
总结
该一键部署脚本可快速搭建完整的 LNMP 环境(Nginx + PHP + MySQL),并支持 HTTPS 自动化配置。
主要优势包括:
全自动安装与配置
支持多站点和多版本 PHP
默认开启安全与性能优化
一键申请并自动续期 SSL 证书
适用于个人网站与中小型项目
通过该脚本,用户可以在几分钟内从零搭建出可直接上线的 Web 环境,极大地简化了服务器配置和网站部署的复杂度。
脚本如下:
#!/usr/bin/env bash
set -euo pipefail
# ============ tiny utils ============
log() { printf "\n\033[1;32m[OK]\033[0m %s\n" "$*"; }
info() { printf "\033[1;34m[i]\033[0m %s\n" "$*"; }
warn() { printf "\033[1;33m[!]\033[0m %s\n" "$*"; }
err() { printf "\033[1;31m[x]\033[0m %s\n" "$*"; }
ask() { read -r -p "$1" "$2"; }
default() { # default <var> <fallback>
if [ -z "${!1:-}" ]; then eval "$1=\"$2\""; fi
}
require_root() {
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
err "请使用 root 或 sudo 运行。"
exit 1
fi
}
# ============ inputs ============
require_root
echo "=== 一键部署 Nginx + PHP-FPM + Docker MySQL(可选 HTTPS)==="
ask "请输入网站根目录(例如 /www/wwwroot/inputyoururl.in): " WEBROOT
WEBROOT="${WEBROOT%/}"
ask "请输入站点域名(例如 example.com,不带 http/https): " DOMAIN
ask "是否添加 www 子域(y/n,默认 y): " ADD_WWW || true
default ADD_WWW "y"
ask "是否申请并自动配置 Let's Encrypt HTTPS(y/n,默认 y): " ENABLE_SSL || true
default ENABLE_SSL "y"
if [[ "${ENABLE_SSL}" =~ ^[Yy]$ ]]; then
ask "用于证书通知的邮箱(例如 you@example.com): " CERT_EMAIL
fi
ask "MySQL 版本(默认 5.7,可填 8.0): " MYSQL_VER || true
default MYSQL_VER "5.7"
ask "MySQL 容器名(默认 mysql57): " MYSQL_NAME || true
default MYSQL_NAME "mysql57"
ask "MySQL 数据目录(默认 ${WEBROOT}/mysql): " MYSQL_DATADIR || true
default MYSQL_DATADIR "${WEBROOT}/mysql"
# 数据库信息(用于 WordPress 等)
ask "是否创建站点数据库和用户(y/n,默认 y): " CREATE_DB || true
default CREATE_DB "y"
if [[ "${CREATE_DB}" =~ ^[Yy]$ ]]; then
ask "数据库名(默认 ${DOMAIN//./_}): " DB_NAME || true
default DB_NAME "${DOMAIN//./_}"
ask "数据库用户名(默认 ${DB_NAME}): " DB_USER || true
default DB_USER "${DB_NAME}"
ask "数据库用户密码(留空则随机生成): " DB_PASS || true
fi
# ============ prepare ============
log "创建网站目录:${WEBROOT}"
mkdir -p "${WEBROOT}"
chown -R "$SUDO_USER:${SUDO_USER:-$USER}" "${WEBROOT}" || true
# ============ apt basics ============
log "更新 apt 并安装常用组件"
export DEBIAN_FRONTEND=noninteractive
apt update -y
apt install -y ca-certificates curl gnupg lsb-release software-properties-common
# ============ install nginx ============
log "安装 Nginx"
apt install -y nginx
systemctl enable nginx
systemctl start nginx
# ============ install PHP & extensions ============
log "安装 PHP-FPM 与常用扩展(自动选系统默认版本)"
apt install -y php php-fpm php-mysql php-cli php-curl php-xml php-gd php-mbstring php-zip
PHP_FPM_SOCK="$(find /run/php/ -maxdepth 1 -type s -name "php*-fpm.sock" 2>/dev/null | head -n1 || true)"
if [ -z "$PHP_FPM_SOCK" ]; then
warn "未找到 PHP-FPM sock,将使用 127.0.0.1:9000。"
PHP_BACKEND="127.0.0.1:9000"
else
PHP_BACKEND="unix:${PHP_FPM_SOCK}"
fi
systemctl enable php*-fpm.service || true
systemctl restart php*-fpm.service || true
# ============ install docker ============
if ! command -v docker >/dev/null 2>&1; then
log "安装 Docker Engine"
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
$(. /etc/os-release; echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update -y
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable docker
systemctl start docker
else
info "Docker 已安装,跳过。"
fi
# ============ run mysql in docker ============
log "准备 MySQL 数据目录:${MYSQL_DATADIR}"
mkdir -p "${MYSQL_DATADIR}"
chown -R 999:999 "${MYSQL_DATADIR}" || true # mysql 用户常为 999
if [ -z "${DB_PASS:-}" ]; then
DB_PASS=$(openssl rand -base64 18 | tr -d '\n/+' | cut -c1-20)
fi
MYSQL_ROOT_PASSWORD=$(openssl rand -base64 24 | tr -d '\n/+' | cut -c1-24)
log "启动 MySQL 容器(mysql:${MYSQL_VER},容器名:${MYSQL_NAME},端口 3306)"
if docker ps -a --format '{{.Names}}' | grep -qx "${MYSQL_NAME}"; then
warn "容器 ${MYSQL_NAME} 已存在,跳过创建。"
else
docker run -d \
--name "${MYSQL_NAME}" \
-e MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD}" \
-p 3306:3306 \
-v "${MYSQL_DATADIR}:/var/lib/mysql" \
"mysql:${MYSQL_VER}"
fi
# 等待 MySQL 就绪
info "等待 MySQL 服务就绪(容器:${MYSQL_NAME})..."
for i in {1..30}; do
if docker exec "${MYSQL_NAME}" mysqladmin ping -uroot -p"${MYSQL_ROOT_PASSWORD}" --silent >/dev/null 2>&1; then
break
fi
sleep 2
done
if [[ "${CREATE_DB}" =~ ^[Yy]$ ]]; then
log "创建数据库及用户:${DB_NAME} / ${DB_USER}"
docker exec -i "${MYSQL_NAME}" mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" <<SQL
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '${DB_USER}'@'%' IDENTIFIED BY '${DB_PASS}';
GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_USER}'@'%';
FLUSH PRIVILEGES;
SQL
fi
# ============ nginx vhost ============
VHOST="/etc/nginx/sites-available/${DOMAIN}.conf"
WWW_SERVER_NAMES="${DOMAIN}"
if [[ "${ADD_WWW}" =~ ^[Yy]$ ]]; then
WWW_SERVER_NAMES="${DOMAIN} www.${DOMAIN}"
fi
log "生成 Nginx 站点配置:${VHOST}"
cat > "${VHOST}" <<NGINX
# ${DOMAIN} - generated by setup_web_stack.sh
# HTTP: ACME + redirect
server {
listen 80;
listen [::]:80;
server_name ${WWW_SERVER_NAMES};
# ACME challenge
location ^~ /.well-known/acme-challenge/ {
root ${WEBROOT%/*}; # e.g., /www
allow all;
default_type "text/plain";
}
# Redirect all to HTTPS
location / {
return 301 https://\$host\$request_uri;
}
access_log /var/log/nginx/${DOMAIN}.http.access.log;
error_log /var/log/nginx/${DOMAIN}.http.error.log;
}
NGINX
# 先只启用 80,待签证书后生成 443
ln -sf "${VHOST}" "/etc/nginx/sites-enabled/${DOMAIN}.conf"
nginx -t
systemctl reload nginx
# ============ certbot (optional) ============
if [[ "${ENABLE_SSL}" =~ ^[Yy]$ ]]; then
log "安装 Certbot 并申请证书"
apt install -y certbot python3-certbot-nginx
# 先仅签发,不自动改配置(我们手动写 443)
certbot certonly --nginx -m "${CERT_EMAIL}" --agree-tos -d "${DOMAIN}" $( [[ "${ADD_WWW}" =~ ^[Yy]$ ]] && echo "-d www.${DOMAIN}" ) --non-interactive || {
warn "自动签发失败,可稍后重试: certbot certonly --nginx -d ${DOMAIN} -d www.${DOMAIN}"
}
if [ -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]; then
log "写入 HTTPS server 块并启用"
cat >> "${VHOST}" <<'NGINX'
# HTTPS site
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name SERVER_NAMES_PLACEHOLDER;
root WEBROOT_PLACEHOLDER;
index index.php index.html index.htm;
ssl_certificate /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/chain.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# WordPress pretty permalinks
location / {
try_files $uri $uri/ /index.php?$args;
}
rewrite /wp-admin$ $scheme://$host$uri/ permanent;
# PHP via PHP-FPM
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass PHP_BACKEND_PLACEHOLDER;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Static cache
location ~* \.(gif|jpg|jpeg|png|bmp|webp|svg|ico|swf)$ { expires 30d; access_log off; }
location ~* \.(js|css)$ { expires 12h; access_log off; }
# Deny sensitive files
location ~ ^/(\.user\.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README\.md)$ {
return 404;
}
# Allow ACME challenge over HTTPS too
location ^~ /.well-known/acme-challenge/ {
root WEBROOT_PARENT_PLACEHOLDER;
allow all;
default_type "text/plain";
}
access_log /var/log/nginx/DOMAIN_PLACEHOLDER.access.log;
error_log /var/log/nginx/DOMAIN_PLACEHOLDER.error.log;
}
NGINX
# 替换占位符
sed -i "s#SERVER_NAMES_PLACEHOLDER#${WWW_SERVER_NAMES}#g" "${VHOST}"
sed -i "s#DOMAIN_PLACEHOLDER#${DOMAIN}#g" "${VHOST}"
sed -i "s#WEBROOT_PLACEHOLDER#${WEBROOT}#g" "${VHOST}"
sed -i "s#WEBROOT_PARENT_PLACEHOLDER#${WEBROOT%/*}#g" "${VHOST}"
sed -i "s#PHP_BACKEND_PLACEHOLDER#${PHP_BACKEND//\//\\/}#g" "${VHOST}"
nginx -t
systemctl reload nginx
else
warn "未检测到证书文件,暂未启用 443。可手动执行: certbot --nginx -d ${DOMAIN} -d www.${DOMAIN}"
fi
else
warn "跳过 HTTPS 配置。可随时运行: certbot --nginx -d ${DOMAIN} -d www.${DOMAIN}"
fi
# ============ summary ============
log "部署完成!"
echo
echo "站点目录: ${WEBROOT}"
echo "域名: ${DOMAIN}"
if [[ "${ENABLE_SSL}" =~ ^[Yy]$ ]]; then
echo "证书路径: /etc/letsencrypt/live/${DOMAIN}/"
fi
echo "Nginx 配置: ${VHOST} -> /etc/nginx/sites-enabled/${DOMAIN}.conf"
echo
echo "MySQL 容器: ${MYSQL_NAME}"
echo "MySQL 版本: ${MYSQL_VER}"
echo "MySQL root 密码: ${MYSQL_ROOT_PASSWORD}"
if [[ "${CREATE_DB}" =~ ^[Yy]$ ]]; then
echo "数据库:${DB_NAME} 用户:${DB_USER} 密码:${DB_PASS}"
fi
echo
echo "测试站点: curl -I http://${DOMAIN}"
[[ "${ENABLE_SSL}" =~ ^[Yy]$ ]] && echo "测试 HTTPS: curl -I https://${DOMAIN}"
echo
echo "如 PHP-FPM 套接字不匹配,可改 ${VHOST} 中的 fastcgi_pass 为实际值(当前:${PHP_BACKEND}) 并执行:"
echo " sudo nginx -t && sudo systemctl reload nginx"
PonyTechLab