
PHP云系统搭建教程:从环境配置到项目部署
把 PHP 项目部署到云服务器,通常不是安装一个 PHP 软件包就结束了。还需要配置 Web 服务器、PHP 运行进程、数据库、域名和 HTTPS,并明确哪些文件可以被公网访问、哪些数据必须留在服务器内部。本文以 Ubuntu 24.04、Nginx、PHP 8.3、MariaDB 为例,搭建一个能运行的最小项目,再说明如何将同一套思路用于现有 PHP 应用。
文中的命令适合有 SSH 管理权限的 Linux 云主机。不同云厂商的控制台、网络规则和镜像可能不同;如果使用托管 PHP、容器平台或已有运维规范,应以对应平台文档为准。示例不包含高可用、自动扩缩容或完整的生产级监控方案。
一、先明确“云系统”的范围
这里的“PHP 云系统”指部署在云服务器或云平台上的 PHP 网站或业务应用,例如企业后台、内容管理系统、接口服务和内部工具。它不等同于云厂商的数据库、对象存储或云原生平台;这些服务可以作为应用的外部依赖,但需要单独配置访问权限、费用和备份策略。
- 适合的场景:已有 PHP 项目需要上线;单台或少量服务器运行中小型网站;需要自行控制运行环境、数据库和发布过程。
- 不适合直接照搬的场景:要求多机容灾、严格合规、超大并发或自动弹性伸缩的系统。此类项目还需要负载均衡、共享会话或外部缓存、数据库高可用、集中日志与故障演练。
- 重要边界:云服务器只提供计算和网络资源,不会自动保证代码安全、数据库可恢复或服务持续可用。HTTPS、访问控制、更新和备份仍需自行负责。
二、准备云服务器和域名
创建一台 Ubuntu 24.04 LTS 云服务器。学习或小型项目可以从 2 核 CPU、2GB 内存起步,但实际容量取决于 PHP 请求耗时、数据库大小、并发量和后台任务。不要把示例规格理解成性能保证。
在云厂商安全组或防火墙中,通常只需开放:
- TCP 22:SSH 管理。建议限制来源为自己的固定 IP;如果没有固定 IP,也应使用密钥登录并按需调整策略。
- TCP 80:HTTP,用于网站访问和证书签发验证。
- TCP 443:HTTPS。
不要将 MariaDB 的 3306 端口直接对全网开放。SSH 的默认端口是否修改,不是安全的替代措施;重点是密钥认证、来源限制和及时更新系统。登录服务器后先更新软件包:
sudo apt update
sudo apt upgrade -y
创建独立的部署用户并使用 SSH 密钥,是比长期使用 root 远程登录更好的管理方式。不同云镜像默认用户名称可能是 ubuntu、debian 或厂商自定义账号;应以创建实例时的说明为准。修改 SSH 配置前,先确认新用户能登录,并保留当前 SSH 会话,避免配置错误导致无法管理。
三、安装 Nginx、PHP-FPM 和 MariaDB
在 Ubuntu 24.04 官方软件源中,可安装 PHP 8.3 及其 FPM、MySQL 驱动。若项目指定其他 PHP 版本,应先核对系统源和项目依赖,不要未经测试就跨大版本升级。
sudo apt install -y nginx mariadb-server \
php8.3-fpm php8.3-cli php8.3-mysql php8.3-curl \
php8.3-mbstring php8.3-xml php8.3-zip
检查服务状态和 PHP 版本:
sudo systemctl status nginx
sudo systemctl status php8.3-fpm
php -v
php -m
项目可能还需要 gd、intl、redis 等扩展。应根据项目的 composer.json、安装说明或 composer check-platform-reqs 检查结果安装,而不是把所有扩展都装上。生产服务器通常应启用 FPM,不应通过 Nginx 直接执行 PHP。
四、创建数据库和最小应用
MariaDB 的本地管理通常使用系统授权机制。进入数据库控制台:
sudo mariadb
创建仅供本项目使用的数据库账号。把示例密码替换为随机生成的强密码,不要复制示例字符串用于真实服务:
CREATE DATABASE cloudapp
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE USER 'cloudapp_user'@'localhost'
IDENTIFIED BY '替换为随机生成的强密码';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX
ON cloudapp.* TO 'cloudapp_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
上述授权适合演示建表和常规增删改查。真实项目可把部署迁移账号与运行时账号分开:运行时账号只保留必要权限,迁移时再临时使用建表权限。不要使用数据库 root 账号连接网站,也不要把数据库暴露到公网。
建立项目目录。这里将公开入口放在 public,配置文件放在网站根目录之外:
sudo mkdir -p /var/www/cloudapp/public
sudo mkdir -p /var/www/cloudapp/shared
sudo chown -R "$USER":www-data /var/www/cloudapp
sudo chmod 750 /var/www/cloudapp /var/www/cloudapp/shared
sudo chmod 750 /var/www/cloudapp/public
创建数据库配置文件:
sudo nano /var/www/cloudapp/shared/config.php
写入以下内容,并替换密码。配置文件是 PHP 文件,不能放在 public 目录中。目录权限应允许 PHP-FPM 用户读取,但不应对所有本地用户开放:
<?php
return [
'dsn' => 'mysql:host=127.0.0.1;dbname=cloudapp;charset=utf8mb4',
'user' => 'cloudapp_user',
'password' => '替换为随机生成的强密码',
];
sudo chown root:www-data /var/www/cloudapp/shared/config.php
sudo chmod 640 /var/www/cloudapp/shared/config.php
为演示准备一张表。实际项目应通过框架迁移工具或版本化 SQL 管理结构变更:
sudo mariadb
USE cloudapp;
CREATE TABLE notes (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
body VARCHAR(500) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
EXIT;
创建 /var/www/cloudapp/public/index.php,提供列表展示和新增功能:
<?php
declare(strict_types=1);
$config = require '/var/www/cloudapp/shared/config.php';
try {
$pdo = new PDO(
$config['dsn'],
$config['user'],
$config['password'],
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
} catch (PDOException $e) {
error_log('Database connection failed: ' . $e->getMessage());
http_response_code(500);
exit('服务暂时不可用');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$body = trim((string)($_POST['body'] ?? ''));
if ($body === '' || mb_strlen($body, 'UTF-8') > 500) {
http_response_code(422);
exit('内容不能为空,且不能超过500个字符');
}
$stmt = $pdo->prepare('INSERT INTO notes (body) VALUES (:body)');
$stmt->execute(['body' => $body]);
header('Location: /', true, 303);
exit;
}
$notes = $pdo->query(
'SELECT id, body, created_at FROM notes ORDER BY id DESC LIMIT 100'
)->fetchAll();
function h(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>
<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>云端便笺</title>
<h1>云端便笺</h1>
<form method="post">
<label>内容
<input name="body" maxlength="500" required>
</label>
<button type="submit">保存</button>
</form>
<ul>
<?php foreach ($notes as $note): ?>
<li><?= h($note['body']) ?>
<small><?= h($note['created_at']) ?></small>
</li>
<?php endforeach; ?>
</ul>
</html>
代码使用 PDO 预处理语句,避免把用户输入直接拼接进 SQL;输出时通过 htmlspecialchars 转义,降低内容被浏览器当作 HTML 执行的风险。输入长度限制是应用层校验,不代替数据库约束,也不等同于完整的业务校验。这个演示没有登录、权限控制、CSRF 防护和限流,因此不能直接作为多用户生产系统使用。
五、配置 Nginx,让公网只访问公开目录
将 example.com 换成自己的域名,并先在 DNS 中把域名的 A 记录指向服务器公网 IPv4 地址。如果使用 IPv6,还需正确配置 AAAA 记录和对应网络规则。
创建站点配置:
sudo nano /etc/nginx/sites-available/cloudapp
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/cloudapp/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri =404;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~ /\.(?!well-known) {
deny all;
}
}
root 指向 public,这样配置文件和项目内部文件不会因为路径配置失误而直接暴露。该示例用于普通 PHP 页面;如果是 Laravel、Symfony 等框架,还应按框架要求配置入口文件、重写规则和静态资源目录。
启用站点并检查配置:
sudo ln -s /etc/nginx/sites-available/cloudapp \
/etc/nginx/sites-enabled/cloudapp
sudo nginx -t
sudo systemctl reload nginx
如果默认站点占用了相同域名或造成访问到错误站点,可以检查 /etc/nginx/sites-enabled/,确认没有不需要的默认配置。修改配置后先执行 nginx -t,通过后再 reload;不要在配置错误时直接重启服务。
六、检查权限并测试应用
Nginx 和 PHP-FPM 在 Ubuntu 软件包的默认配置中通常以 www-data 用户运行。先测试网页目录和配置文件是否可读:
sudo -u www-data test -r /var/www/cloudapp/public/index.php \
&& echo "入口文件可读"
sudo -u www-data test -r /var/www/cloudapp/shared/config.php \
&& echo "配置文件可读"
不需要给整个项目目录设置 777。只有应用确实需要写入的目录,例如上传文件或缓存目录,才应授予 PHP-FPM 写权限,并限制该目录中的文件类型和可执行能力。用户上传的文件最好放在 Web 根目录之外;如必须公开提供下载,应使用受控的下载接口或专用静态存储。
从浏览器访问域名,或在服务器上用 curl 检查:
curl -I http://example.com
curl -sS http://example.com
如果访问出现 502,通常是 Nginx 找不到 PHP-FPM socket、服务未运行或权限不符;404 可能是域名未命中正确站点、入口文件不存在或路由规则不适用;页面出现“服务暂时不可用”,应查看 PHP-FPM 与 Nginx 日志,而不是在浏览器输出数据库密码或完整异常堆栈。
七、启用 HTTPS
确认域名已经解析到服务器,且安全组放行 80、443 后,可使用 Certbot 的 Nginx 插件申请证书。Ubuntu 24.04 可从发行版软件源安装:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
根据交互提示选择是否将 HTTP 重定向到 HTTPS。证书签发失败时,先核对 DNS 解析、域名拼写和公网端口,不要通过关闭防火墙或开放数据库端口来排错。检查自动续期定时任务:
sudo certbot renew --dry-run
HTTPS 保护浏览器与服务器之间的传输,不会自动加密数据库备份、修复 SQL 注入,也不代表服务器上所有通信都安全。证书续期和域名变更都应纳入维护流程。
八、把示例迁移到真实 PHP 项目
对于使用 Composer 的项目,建议在 CI 构建或发布机上安装依赖,再将可部署产物发布到服务器。常见命令如下,是否能使用 --no-dev 取决于项目是否把运行时依赖错误地放在开发依赖中:
composer install --no-dev --prefer-dist --optimize-autoloader
composer check-platform-reqs
不要在生产服务器上执行未经审查的第三方安装脚本。部署前检查项目版本要求、PHP 扩展、环境配置和数据库迁移;发布后执行健康检查。框架的缓存、队列、定时任务和文件权限配置各不相同,应以项目文档为准。
一个相对可靠的发布过程通常包含:拉取固定版本或发布包、安装依赖、备份数据库、运行迁移、切换当前版本、验证关键功能。不要直接在正在服务的目录中覆盖文件;中断期间可能出现新旧代码混用。小型系统可以用版本目录和符号链接实现切换,但回滚代码并不一定能回滚数据库结构变更,迁移设计要考虑兼容性。
敏感配置不要提交到 Git,也不要写进可公开下载的配置文件。可以使用服务器权限保护的配置文件或平台提供的密钥管理服务;前者需要控制文件读取权限,后者也要限制谁能读取密钥。修改密码后应同步更新配置,并安排数据库账号轮换。
九、日志、备份与日常维护
先熟悉服务日志的实际位置:
sudo journalctl -u nginx --since "30 minutes ago"
sudo journalctl -u php8.3-fpm --since "30 minutes ago"
sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 100 /var/log/nginx/access.log
生产环境应关闭向客户端展示详细错误信息,将异常写入受控日志,并避免在日志中记录密码、令牌、完整支付信息等敏感数据。日志需要轮转和保留策略;只查看日志而不限制其增长,仍可能耗尽磁盘。
数据库备份可用 mariadb-dump 导出。下面命令将 SQL 写到标准输出,备份文件应放到受限目录,并避免将密码直接写在命令行参数里,以免暴露在 shell 历史或进程信息中:
sudo install -d -m 700 /var/backups/cloudapp
sudo mariadb-dump cloudapp \
| gzip \
| sudo tee /var/backups/cloudapp/cloudapp-$(date +%F).sql.gz > /dev/null
sudo chmod 600 /var/backups/cloudapp/cloudapp-$(date +%F).sql.gz
该命令适用于本机 MariaDB 且当前系统用户具备数据库访问权限的情况;若认证方式不同,应按实际账号配置。备份保存在同一台服务器,无法防范服务器丢失或磁盘故障。生产环境应把备份复制到独立存储,限制访问,并定期在隔离环境中实际恢复验证。备份成功不等于可恢复。
还应定期检查系统安全更新、磁盘空间、证书到期、数据库容量和应用错误率。云平台快照可以作为补充,但不能替代数据库一致性备份和恢复演练。
十、常见错误及排查顺序
1. 域名访问到错误页面
先检查 DNS 是否指向正确公网地址,再核对 Nginx 的 server_name、站点是否启用以及安全组是否放行 80/443。多个站点使用同一端口时,Nginx 会根据请求主机名选择站点;域名不匹配时可能落到默认站点。
2. PHP 文件被下载或显示源码
这是高风险配置错误,说明请求没有正确交给 PHP-FPM。立即停止对外访问并检查 Nginx 的 PHP location、FPM socket 路径和服务状态。不要把应用的 PHP 源码、配置或密钥留在公开目录中;修复后还要评估是否已经发生泄露并轮换受影响凭据。
3. Nginx 返回 502 Bad Gateway
检查 systemctl status php8.3-fpm、PHP-FPM 日志和 Nginx 中的 socket 路径。PHP 版本升级后,socket 名称可能改变。若使用 TCP 监听而非 Unix socket,还要确认监听地址与访问控制设置一致。
4. 数据库连接失败或提示拒绝访问
核对配置文件是否可读、数据库名和账号是否正确、数据库服务是否运行,以及账号允许连接的主机范围。使用 localhost 与 127.0.0.1 时,客户端可能采用不同连接方式;不要为绕过问题而给账号授予全网访问权限。
5. 上传、缓存或日志目录出现权限错误
不要用递归 777 解决。确认实际运行用户,再只对确需写入的目录设置所有者或组权限;其余 PHP 代码应保持只读。权限调整前先查明哪个进程需要写入,避免让 Web 进程可以修改全部应用代码。
6. 页面报错但日志里没有应用异常
核对 PHP 错误日志配置、应用日志目录权限和日志级别。还要检查磁盘是否已满、PHP-FPM 是否达到进程上限、请求是否超时。提高超时或进程数会增加资源消耗,不应只为消除报错而无限增大。
结语
PHP 项目上云的基本链路是:云主机提供运行环境,Nginx 接收 HTTP 请求,PHP-FPM 执行 PHP,数据库保存结构化数据,域名和 HTTPS 提供可访问入口。真正决定部署是否可靠的,往往不是安装命令,而是公开目录边界、最小权限、错误处理、备份恢复和可重复发布。
本文示例可以帮助验证一台 Ubuntu 云服务器上的基础 PHP 部署,但便笺程序刻意保持简单,不具备生产系统所需的身份认证、授权、CSRF 防护、审计、限流和高可用能力。上线业务前,应结合数据敏感度、访问规模和恢复目标补齐这些设计,并先在测试环境验证升级与回滚流程。




