logoPonyTechLab

马兆鑫的AI与深度学习博客

一键部署 Nginx + PHP + Docker MySQL + SSL 自动化建站脚本教程

概述

本文介绍如何在一台全新的 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 版本,并安装常用扩展,如:
mysqlicurlxmlgdmbstringzip
可直接运行常见 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 容器与数据库信息


使用步骤

  1. 将脚本保存为:

    setup_web_stack.sh
  2. 赋予执行权限:

    chmod +x setup_web_stack.sh
  3. 执行脚本:

    sudo ./setup_web_stack.sh
  4. 按提示输入网站目录、域名、是否开启 HTTPS、MySQL 版本及数据库信息。

  5. 等待自动安装完成,根据输出信息进行访问与验证。


验证安装

查看服务运行状态:

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"


avatar

Pony

深度学习爱好者和技术研究者。专注于人工智能、边缘计算及计算机视觉领域的开发与应用。

现居地:陕西省-西安市

Email:zhaoxin.ma@chd.edu.cn

Categories