實測環境與硬體規格
| 項目 | 實際測試配置 |
| 測試日期 | 2026-10-02 |
| 作業系統 | Ubuntu 24.04.5 LTS |
| 主機數量 | 1 台新建隔離 VM;下列資源為每台配置 |
| CPU | 2 vCPU |
| 記憶體 | 4 GiB |
| 系統磁碟 | 32 GiB;HDD 儲存、VirtIO、ext4 |
| Linux 核心 | 6.8.0-139-generic |
| WordPress | 7.1.2 |
| PHP | 8.3.6 |
| Nginx | nginx version: nginx/1.24.0 (Ubuntu) |
| MariaDB | mariadb Ver 15.1 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64) using EditLine wrapper |
使用 Nginx、PHP-FPM 與 MariaDB 原生安裝 WordPress,核心放在 /opt,區分核心與上傳檔案權限,驗證登入、真實 HTTP 上傳、備份還原與開機後服務。
文章目錄
本文以全新 Ubuntu 24.04 隔離主機和合成資料實測。套件來自經過簽章驗證、符合 Noble 的套件來源;沒有使用正式資料庫或正式網站的帳號。讀者請依自己的環境確認來源與版本。
圖中是測試主機實際 bash PTY 的執行紀錄,由瀏覽器呈現;提示符統一為 lab$,執行結果未改寫。文字命令另行保留,方便複製。
一、先看清楚每個元件的位置
| 元件 | 本篇配置 |
| WordPress 核心 | /opt/wp-lab/site |
| 私有設定 | /opt/wp-lab/wp-config.php;位於網站根目錄之外 |
| PHP 執行帳號 | wp_lab;system 帳號、nologin |
| PHP-FPM socket | /run/php/wp-lab.sock |
| 網站上傳 | /opt/wp-lab/site/wp-content/uploads |
| 測試入口 | http://blog.example.test:8080;僅本機 loopback |
| 資料庫 | MariaDB,本機 socket,專用資料庫/角色 |
本篇驗證原生安裝,測試 listener 只綁 loopback。對外 HTTPS 與網域不是此篇安裝的一部分;另外使用 Cloudflare Tunnel + Caddy 篇 接上入口。不要直接把這個 HTTP 教學入口當作正式公開管理介面。
二、安裝 Ubuntu 原生套件
sudo apt update
sudo apt install nginx mariadb-server php8.3-fpm php8.3-mysql php8.3-cli \
php8.3-curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip php8.3-intl \
curl unzip gnupg
sudo useradd --system --home /var/lib/wp-lab --shell /usr/sbin/nologin wp_lab
sudo install -d -m 0755 /opt/wp-lab /opt/wp-lab/site /opt/wp-lab/bin
sudo install -d -o root -g wp_lab -m 0750 /opt/wp-lab/private
sudo install -d -o wp_lab -g wp_lab -m 0750 /var/lib/wp-lab /var/lib/wp-lab/sessions
本次從已審核、符合 Noble 的簽章鏡像安裝,沒有加入其他 Ubuntu 版本來源。以下 WordPress 與 WP-CLI 從官方網站取得,並分別核對核心 checksums 與 WP-CLI 的 GPG 簽章。
三、下載核心並驗證官方來源
umask 077
mkdir -p ~/wp-download && cd ~/wp-download
curl -fsSL -o wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
curl -fsSL -o wp-cli.phar.asc https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.asc
curl -fsSL -o wp-cli.pgp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/wp-cli.pgp
gpg --import wp-cli.pgp
gpg --verify wp-cli.phar.asc wp-cli.phar
sudo install -m 0755 wp-cli.phar /opt/wp-lab/bin/wp
curl -fsSL -o wordpress.tar.gz https://wordpress.org/wordpress-7.1.2.tar.gz
tar -tzf wordpress.tar.gz
sudo tar -xzf wordpress.tar.gz --strip-components=1 --no-same-owner -C /opt/wp-lab/site
sudo /opt/wp-lab/bin/wp --allow-root --path=/opt/wp-lab/site core verify-checksums --version=7.1.2 --locale=en_US
先確認 archive 內只有預期的 wordpress/ 路徑,再解壓到全新空白目錄。本次用系統 tar 避免長路徑解壓問題;正式站更新不能重用這段覆蓋既有目錄的流程。安裝時重新查看官方版本與檢查結果,不要把本篇測試版號當成永久最新版本。
四、在本機產生設定與密碼,值不進入命令參數
以下只適用全新教學主機:程式本機產生資料庫與管理密碼,經標準輸入建立 MariaDB 帳號,設定放在網站根目錄外。管理密碼留在 root-only 檔案,不回顯;首次登入透過你自己的安全本機交付方式取用,不放進文章、截圖、Git 或網頁。
sudo python3 - <<'PY'
import json, os, pathlib, secrets, subprocess
root=pathlib.Path('/opt/wp-lab')
assert not (root/'wp-config.php').exists()
dbpass=secrets.token_hex(32)
adminpass=secrets.token_urlsafe(36)
p=root/'private/admin-password'
fd=os.open(p,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
with os.fdopen(fd,'w') as f: f.write(adminpass)
sql="CREATE DATABASE wp_lab CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sql+="CREATE USER 'wp_lab'@'localhost' IDENTIFIED BY '"+dbpass+"';"
sql+="GRANT ALL PRIVILEGES ON wp_lab.* TO 'wp_lab'@'localhost';"
subprocess.run(['mariadb','--batch'],input=sql.encode(),check=True)
cfg="<?php\n"
for name,value in {'DB_NAME':'wp_lab','DB_USER':'wp_lab','DB_PASSWORD':dbpass,
'DB_HOST':'localhost','DB_CHARSET':'utf8mb4','DB_COLLATE':''}.items():
cfg+="define("+json.dumps(name)+","+json.dumps(value)+");\n"
for name in ['AUTH_KEY','SECURE_AUTH_KEY','LOGGED_IN_KEY','NONCE_KEY',
'AUTH_SALT','SECURE_AUTH_SALT','LOGGED_IN_SALT','NONCE_SALT']:
cfg+="define("+json.dumps(name)+","+json.dumps(secrets.token_hex(48))+ ");\n"
cfg+="$table_prefix='wp_'; define('DISALLOW_FILE_EDIT',true);"
cfg+="define('DISALLOW_FILE_MODS',true); define('WP_DEBUG',false);"
cfg+="define('WP_HOME','http://blog.example.test:8080');"
cfg+="define('WP_SITEURL','http://blog.example.test:8080');"
cfg+="define('ABSPATH',__DIR__.'/site/'); require_once ABSPATH.'wp-settings.php';"
config=root/'wp-config.php'; config.write_text(cfg); config.chmod(0o640)
subprocess.run(['chown','root:wp_lab',str(config)],check=True)
print('Private configuration created; secret values are not displayed.')
PY
將 blog.example.test 在測試主機對應 127.0.0.1。Cloud-Init 管理 hosts 的 VM 必須保留該對應;本次補驗過重新開機,範例名稱仍能解析。正式用途改用你管理的 DNS 與 HTTPS 名稱。
五、初始化 WordPress 與檔案權限
sudo tee /opt/wp-lab/private/initialize.php >/dev/null <<'PHP'
<?php
define('WP_INSTALLING',true);
require '/opt/wp-lab/wp-config.php';
require_once ABSPATH.'wp-admin/includes/upgrade.php';
wp_install('Native WordPress Lab','lab_admin','admin'.'@'.'example.invalid',false,'',
file_get_contents('/opt/wp-lab/private/admin-password'));
update_option('users_can_register',0);
update_option('default_comment_status','closed');
update_option('default_ping_status','closed');
update_option('blog_public',0);
update_option('permalink_structure','/%postname%/');
PHP
sudo chmod 0600 /opt/wp-lab/private/initialize.php
sudo php /opt/wp-lab/private/initialize.php
sudo chown -R root:root /opt/wp-lab/site
sudo install -d -o wp_lab -g wp_lab -m 0755 /opt/wp-lab/site/wp-content/uploads
sudo chown -R wp_lab:wp_lab /opt/wp-lab/site/wp-content/uploads
範例電子郵件 [email protected] 不會成為有效的復原收件地址;正式安裝需換成自己掌握的郵件並另行驗證通知。本次沒有設定 SMTP、註冊、付款或會員機制。核心由管理者更新,DISALLOW_FILE_MODS 會關閉管理頁的程式更新與外掛安裝,所以需要自己的更新流程。
六、設定獨立 PHP-FPM pool
; /etc/php/8.3/fpm/pool.d/wp-lab.conf
[wp-lab]
user = wp_lab
group = wp_lab
listen = /run/php/wp-lab.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = 3
pm.process_idle_timeout = 10s
php_admin_value[session.save_path] = /var/lib/wp-lab/sessions
php_admin_flag[display_errors] = off
php_admin_flag[log_errors] = on
七、設定 Nginx 與 permalink
# /etc/nginx/conf.d/wp-lab.conf
server {
listen 127.0.0.1:8080;
server_name blog.example.test;
root /opt/wp-lab/site;
index index.php;
client_max_body_size 4m;
location ~* ^/wp-content/uploads/.*\.php$ { return 403; }
location ~ /\. { deny all; }
location = /wp-config.php { deny all; }
location / { try_files $uri $uri/ /index.php?$args; }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/wp-lab.sock;
}
}
sudo php-fpm8.3 -t
sudo nginx -t
sudo systemctl enable --now nginx php8.3-fpm mariadb
sudo systemctl restart php8.3-fpm nginx
systemctl is-active nginx php8.3-fpm mariadb
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://blog.example.test:8080/

在這台全新教學主機停用未使用的 Nginx default site,避免另有 listener;有其他網站時不能直接照做。實测登入、發佈範例內容、permalink、PHP 真實 HTTP 媒體上傳回傳 201,以及匿名下載圖片的位元組一致。

八、確認核心不可寫,上傳可以工作
sudo -u wp_lab test -w /opt/wp-lab/site/wp-includes/version.php
echo $?
sudo -u wp_lab test -w /opt/wp-lab/site/wp-content/uploads
echo $?
第一個 test 預期非 0,第二個為 0。這是檔案權限檢查;本次還真正以登入後的 HTTP REST 上傳圖片,確認 PHP 使用者擁有檔案並可匿名下載。放進 uploads 的測試 PHP 檔則被 Nginx 403 拒絕,匿名使用者資料 API 回傳 401。
九、備份 SQL 與檔案要成為同一組
測試過程停止 PHP-FPM 以暫停網站寫入,再取得資料庫 dump 和包含核心、上傳與私有設定的檔案包,完成後立即恢復 PHP。正式站應安排維護時間,並將備份加密保存到獨立位置。備份含管理密碼雜湊、設定密碼與 salts,不能上傳到公開網站。
sudo install -d -m 0700 /var/backups/wp-lab
sudo systemctl stop php8.3-fpm
sudo sh -c 'umask 077; mariadb-dump --single-transaction --routines --triggers wp_lab > /var/backups/wp-lab/site.sql'
sudo tar -czf /var/backups/wp-lab/site.tar.gz -C /opt/wp-lab site wp-config.php
sudo chmod 0600 /var/backups/wp-lab/site.tar.gz
sudo systemctl start php8.3-fpm
本次將 SQL 還原到全新獨立資料庫、將檔案解壓到另一個私有目錄,核對範例文章、上傳紀錄與圖片位元組。也實際重新開機,驗證服務、名稱解析、文章與登入。還原測試沒有覆蓋原站,也沒有宣稱驗證了另一台主機的完整網站切換。
這次驗證了什麼
| 驗證項目 | 結果 |
| WP-CLI 官方 GPG 簽章與核心 checksums | 通過 |
| 原生 Nginx/PHP-FPM/MariaDB 正常 | 通過 |
| HTTP 登入與 permalink 正常 | 通過 |
| 真實 HTTP 上傳 201、PHP 擁有檔案 | 通過 |
| 匿名 API 401、uploads PHP 403 | 通過 |
| 配對 SQL 與圖片還原一致 | 通過 |
| 重新開機後服務、名稱與登入正常 | 通過 |
實測範圍:原生服務、私有 loopback HTTP、核心 checksums、真實登入/上傳、檔案權限、配對 SQL/圖片還原及重新開機均驗證。沒有測正式 HTTPS、SMTP、付費外掛、會員收費、跨主機切換或正式流量容量。