SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

Ubuntu 24.04 安裝 LAMP 與 PHP-FPM 教學

在 Ubuntu 24.04 建置 Apache、MariaDB 與 PHP 8.3 PHP-FPM,設定名稱型虛擬主機、unix_socket 驗證,並用 Certbot 取得免費 HTTPS 憑證。

您將建置的內容

LAMP 架構是在單一 Ubuntu 24.04 伺服器上運作的四個組件:底層的 Linux、負責處理 HTTP 的 Apache、儲存資料的 MariaDB,以及執行程式碼的 PHP 8.3。完成後,您將擁有一個基於名稱的虛擬主機來服務實際的應用程式目錄、一個具備最小權限專用使用者的資料庫、透過 PHP-FPM 連接至 Apache 的 PHP,以及一個頂層的免費 Let's Encrypt 憑證。

安裝過程僅需四個 apt 指令。本指南絕大部分內容在於組件間的串接,以及初學者常犯、導致新架構出現空白頁面、將原始碼以下載方式傳送至瀏覽器,或無法存取剛安裝之資料庫的錯誤。上述每個問題都有可辨識的特徵,下方列出了每個問題及其對應的確切錯誤訊息。

先決條件與注意事項

假設您已準備好一台全新的 Ubuntu 24.04 KVM VPS,並擁有 sudo 使用者權限或 root 權限,以及一個公開的 IPv4 位址。最小化堆疊在 1 GB RAM 下即可執行;若要部署實際的資料庫應用程式,請至少配置 2 GB RAM,因為 MariaDB 的預設緩衝區加上少量的 PHP-FPM 工作程序會迅速耗盡前 1 GB 記憶體。

在執行最後的 Certbot 步驟前,必須滿足以下兩項條件,請務必先行確認。您需要一個指向該 VPS 公開 IP 的 domain name A 記錄;Let's Encrypt 會透過 HTTP 驗證該網域名稱,僅使用裸 IP 位址無法取得憑證。此外,必須確保網際網路可連線至 80 與 443 連接埠;對於許多雲端服務供應商而言,這意味著除了在伺服器內部的 ufw 設定外,還必須在控制台的網路防火牆中開啟這些連接埠。DNS 變更生效可能需要長達 1 小時,請優先設定 A 記錄,待進行後續步驟時即可生效。

Step 1 - 安裝 Apache 並確認預設頁面

sudo apt update
sudo apt install -y apache2

apt 會為您啟動並啟用該服務。請檢查狀態:

systemctl status apache2

您應會看到一行顯示 active (running) 的內容。現在,請在瀏覽器中開啟 http://YOUR_SERVER_IP/。若看到帶有「It works!」大標題的 Apache2 Ubuntu Default Page,即代表安裝成功,這證明 Apache 正在運作且無錯誤。該頁面位於 /var/www/html/index.html,並由軟體內建的預設虛擬主機 000-default.conf 提供服務。您稍後會將其停用;目前它們的存在正是您預期看到的結果。

如果頁面無法載入,但 systemctl 顯示該程序正在執行,則表示防火牆阻擋了連線。這將是下一步要處理的問題。

步驟 2 - 開放 HTTP 與 HTTPS 的防火牆規則

apache2 套件會註冊三個 ufw 應用程式設定檔。請列出它們:

sudo ufw app list

您將會看到 Apache、Apache Full 與 Apache Secure。Apache 僅針對 port 80,Apache Secure 僅針對 443,而 Apache Full 則包含兩者;由於您稍後會加入 TLS,因此這是您需要的設定檔。

sudo ufw allow OpenSSH
sudo ufw allow "Apache Full"
sudo ufw enable

請務必在執行 ufw enable 之前 先允許 OpenSSH。ufw 預設會拒絕所有傳入流量;若未先設定 SSH 規則就啟用它,防火牆會在啟動瞬間切斷您的連線。雖然您能維持當前的連線階段,但之後將無法重新連線。請使用 sudo ufw status 進行確認;您應確保 OpenSSH、Apache Full 及其對應的 v6 版本皆顯示為 ALLOW。

步驟 3 - 安裝並強化 MariaDB

sudo apt install -y mariadb-server
systemctl status mariadb

Ubuntu 24.04 隨附 MariaDB 10.11,這是一個長期支援版本,因此無需使用外部儲存庫。在服務執行後,請進行強化:

sudo mysql_secure_installation

建議仔細閱讀提示,而非直接按 Enter。當系統詢問 current root password 時,請按 Enter,因為目前尚未設定密碼。當詢問 "Switch to unix_socket authentication?" 時,由於此套件已預設啟用該功能,選擇任何選項皆無影響,請按 n。針對 "Change the root password?",請回答 n(原因詳見下一段),接著對其餘問題回答 Y:移除匿名使用者、禁止遠端 root 登入、刪除測試資料庫並重新載入權限資料表。

這是最容易讓人困惑的部分。在 Ubuntu 的 MariaDB 中,root 資料庫帳號使用 unix_socket 驗證,而非密碼。這代表資料庫會信任您已通過驗證的「作業系統使用者」。因此,從 root shell 執行以下指令即可運作:

sudo mysql

...系統會將您帶入 MariaDB [(none)]> 提示字元,且不會要求輸入密碼。若以無權限的使用者執行相同指令則會被拒絕,這正是其核心目的:資料庫 root 的存取權限與主機上的 sudo 綁定,且沒有密碼可供竊取、釣魚或暴力破解。這種方式比密碼更安全,請勿更動。由此衍生的規則為:絕對不要將應用程式指向 root 帳號。請為每個應用程式建立專屬使用者(步驟 7),因為透過 TCP 連線且使用帳號密碼的應用程式無法使用 socket 驗證,且您應將每個應用程式限制在其專屬的資料庫中。

步驟 4 - 安裝 PHP 8.3 與 PHP-FPM

Ubuntu 24.04 預設的 PHP 版本為 8.3。請安裝 FPM 進程管理器以及典型應用程式所需的擴充功能:

sudo apt install -y php8.3-fpm php8.3-mysql php8.3-cli \
  php8.3-curl php8.3-xml php8.3-mbstring php8.3-zip

請注意清單中未包含的項目:libapache2-mod-php。該舊版套件會將 PHP 直譯器嵌入每個 Apache 進程中。雖然配置簡單,但每個工作進程(worker)無論是處理腳本還是靜態圖片,都會攜帶一份 PHP 副本,且兩者共用生命週期;此外,它僅能與 Apache 效率最低的 prefork MPM 搭配使用。相對地,PHP-FPM 將 PHP 作為獨立的進程池執行,Apache 透過 Socket 與其通訊。如此一來,Apache 可使用多執行緒的 event MPM 處理靜態檔案,僅將 PHP 請求轉發給 FPM;進程池可獨立於網頁伺服器進行調校,且未來若改用 nginx 作為前端,這套 FPM 設定依然適用。這是目前作為預設方案的合理原因。

Apache 透過 proxy_fcgi 模組連接 FPM。請啟用該模組、啟用 FPM 套件提供的設定檔,並重新啟動服務:

sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2

a2enconf php8.3-fpm 會啟用 /etc/apache2/conf-available/php8.3-fpm.conf,其中包含將 PHP 檔案路由至 FPM Socket 的規則。其核心邏輯是比對所有 .php 檔案,並將其轉發至 /run/php/php8.3-fpm.sock 的 Socket:

<FilesMatch ".+\.ph(ar|p|tml)$">
    SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>

您無需編輯該檔案,其預設設定即為正確。但了解 Socket 路徑有助於診斷後續發生的「PHP 顯示為下載而非執行」以及「Primary script unknown」錯誤;這兩類問題通常皆源於 Apache 與 FPM 對此 Socket 或其後端檔案的設定不一致。

步驟 5 - 為您的應用程式設定名稱式虛擬主機

名稱式虛擬主機允許單一 IP 位址服務多個網站;Apache 會根據請求中的 Host: 標頭來選擇網站。請在遠離預設 /var/www/html 的位置為應用程式建立目錄:

sudo mkdir -p /var/www/testapp
sudo chown -R www-data:www-data /var/www/testapp
sudo chmod -R 755 /var/www/testapp

擁有權至關重要。在 Ubuntu 上,Apache 與 PHP-FPM 皆以 www-data 使用者身分執行,因此網頁伺服器必須讀取的檔案,以及應用程式必須寫入的目錄(例如上傳資料夾),其擁有者應設為 www-data。若您同時會以登入使用者身分編輯檔案,常見的做法是將檔案擁有權設為您自己,並將您的使用者加入 www-data 群組;若僅是單純部署,使用 www-data:www-data 是最不容易出錯的方式。

在 /etc/apache2/sites-available/testapp.conf 建立虛擬主機:

<VirtualHost *:80>
    ServerName app.example.com
    DocumentRoot /var/www/testapp

    <Directory /var/www/testapp>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/testapp-error.log
    CustomLog ${APACHE_LOG_DIR}/testapp-access.log combined
</VirtualHost>

將 ServerName 設定為您的真實網域。Options -Indexes 可防止 Apache 在沒有索引檔案時列出目錄內容,否則訪客將能瀏覽您的原始碼樹狀結構。AllowOverride All 可讓 .htaccess 檔案生效,大多數 PHP 應用程式皆依賴此功能來實現美觀的 URL;若您的應用程式不需要此功能,可將其設為 None 以獲得些微效能提升。啟用此網站、停用預設網站、檢查設定檔並重新載入:

sudo a2ensite testapp
sudo a2dissite 000-default
sudo apache2ctl configtest
sudo systemctl reload apache2

apache2ctl configtest 應顯示 Syntax OK。a2dissite 000-default 這一行是人們最容易遺忘的部分,這也是為何預設頁面稍後會看起來像是卡住的原因,相關說明請參閱故障排除章節。

步驟 6 - 驗證 PHP 執行狀況並刪除測試檔案

在應用程式根目錄中建立一個單行的 PHP 檔案:

echo "<?php phpinfo();" | sudo tee /var/www/testapp/info.php

瀏覽 http://app.example.com/info.php。若設定正確,您將會看到一個長條狀的紫色與灰色 PHP Version 8.3.x 表格,其中列出了已載入的模組,且 Server API 這一行應顯示 FPM/FastCGI。最後這一行確認了請求是透過 PHP-FPM 處理,而非 mod_php。

現在請立即刪除該檔案:

sudo rm /var/www/testapp/info.php

phpinfo() 會暴露您精確的 PHP 版本、所有已載入的擴充功能、檔案路徑以及環境詳細資訊,這對任何試圖探測伺服器是否存在已知漏洞的人來說是極大的便利。這僅是測試工具,而非功能。確認頁面後請務必立即刪除。若瀏覽器未顯示表格而是提示您 下載 info.php,代表 PHP 未正確連結至 Apache;請在進行任何其他操作前,先跳轉至故障排除章節。

步驟 7 - 建立應用程式資料庫與最小權限使用者

以 socket 驗證的 root 身分開啟資料庫:

sudo mysql

接著建立一個資料庫,並建立僅限於該資料庫的使用者:

CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

此處有三個刻意的選擇。utf8mb4 是真正的四位元組 UTF-8,舊有的 utf8 別名會無聲地截斷表情符號與部分 CJK 字元,因此請務必使用 utf8mb4。權限授予範圍為 appdb.*,而非 *.*:此使用者僅能存取其專屬資料庫,無法存取其他內容,因此即便應用程式出現 SQL 注入漏洞,也無法讀取其他網站的資料表。此外,'appuser'@'localhost' 將該帳號限制為僅能從本機發起的連線。

以該使用者身分進行測試:

mysql -u appuser -p appdb

系統會要求輸入密碼,並將您帶入 MariaDB [appdb]> 提示字元。請注意,此處沒有使用 -h 旗標;省略該旗標時,用戶端會透過本機 Unix socket 進行連線,這正是 MariaDB 所認定的 localhost。有一個值得注意的陷阱:對於 MySQL 與 MariaDB 而言,localhost 代表 Unix socket,而 127.0.0.1 代表 TCP 連線。 在標準的 Ubuntu 24.04 MariaDB 上,伺服器仍會將來自 127.0.0.1 的 TCP 連線解析回 localhost,因此兩者皆符合該帳號設定。但在啟用 skip-name-resolve 的伺服器上(這是一種常見的效能調整,也是許多容器映像檔的預設值),兩者會被視為不同的主機,若應用程式連線至 127.0.0.1,即使密碼正確也會被拒絕並顯示 ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' (using password: YES)。

因此,請將您的應用程式指向主機 localhost、使用者 appuser、資料庫 appdb,切勿指向 root。當主機為字串 localhost 時,PHP 的 mysqli 與 PDO 皆會切換至 Unix socket,這與您剛建立的帳號相符。若框架強制要求使用數值 TCP 主機,請依照其實際連線方式建立使用者(例如 'appuser'@'127.0.0.1'),或僅在必須從其他機器存取資料庫時,才使用 @'%'(並搭配防火牆規則)。

步驟 8 - 使用 Certbot 加入 HTTPS

透過純 HTTP 提供登入表單時,密碼會以明文傳送,而且所有現代瀏覽器都會將頁面標示為「不安全」。Certbot 只需一個指令即可解決這個問題。請搭配 Apache 外掛程式安裝:

sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache

Certbot 在此使用 2 個外掛程式。apache 驗證器會透過正在執行的 Apache 短暫提供 challenge 檔案,以證明您控制該網域;接著,apache 安裝程式會改寫您的 virtual host,加入 443 區塊、將其指向新的憑證,並預設將所有 HTTP 流量重新導向至 HTTPS。自 1.4.0 版(2020 年 5 月)起,Certbot 不再詢問是否重新導向,因此若需要繼續提供純 HTTP,請傳入 --no-redirect。由於您已在步驟 5 設定真正的 ServerName,Certbot 會自動偵測該網域。今天核發的憑證有效期為 90 天(Let's Encrypt 將在 2027 年 2 月 10 日把預設有效期縮短至 64 天,並在 2028 年 2 月 16 日縮短至 45 天),而套件會安裝負責續期的 systemd timer;請使用 sudo certbot renew --dry-run 驗證該 timer,其結尾應為 Congratulations, all simulated renewals succeeded。

如需完整瞭解 challenge、續期 timer,以及 DNS 與防火牆需求,請參閱配套指南:在 Apache 上使用 Certbot 核發免費 Let's Encrypt TLS 憑證。

備份、升級與強化

備份保存狀態的兩項內容:資料庫與網站根目錄。每晚執行邏輯備份是最簡單且可靠的方法,sudo sh -c 'mysqldump --all-databases --single-transaction | gzip > /root/db-$(date +%F).sql.gz',然後將備份複製到主機外。將整個流程包在 sudo sh -c 中很重要:否則 shell 會以目前使用者的身分執行 > /root/... 重新導向,並因 Permission denied 而失敗,因為只有 mysqldump 繼承了 sudo。--single-transaction 可在不鎖定 InnoDB 資料表的情況下建立一致的快照。再搭配 tar 的 /var/www 與 /etc/apache2/sites-available,即可從這些檔案在新的 VPS 上重建整個堆疊。

升級是一般的 sudo apt update && sudo apt upgrade。最容易造成問題的是版本升級時的 PHP 版本升級。Ubuntu 26.04 LTS 預設提供 PHP 8.5,而本指南中安裝的 php8.3-* 套件在該版本中不存在。因此,升級後會執行 php8.5-fpm,socket 會變成 /run/php/php8.5-fpm.sock,但 Apache 設定仍指向 8.3 socket。啟用新的設定檔(sudo a2enconf php8.5-fpm)並停用舊設定檔,否則網站可能在其他部分都正常的升級後開始回傳 Primary script unknown。由於 PHP 發布版本的速度快於 LTS 發行版,請查閱目前的 PHP 發行說明,不要固定在特定修補版本。

第一天就值得完成兩項強化措施。首先,在主機上設定 讓 Fail2Ban 監控 SSH。公開 VPS 通常在數分鐘內就會遭遇自動化登入嘗試,而設定一個小型 jail 即可在封鎖前,將數千次嘗試減少為少數幾次。其次,如果你希望透過瀏覽器管理 Apache 虛擬主機、MariaDB 資料庫與使用者,而不是手動編輯檔案,Webmin 網頁式控制面板可在這個確切的堆疊上運作,並管理你剛才建立的相同設定檔。兩者都不能取代對各個元件的理解,但都能降低日常管理的繁瑣程度。

失敗模式與您會看到的訊息

預設頁面不會消失。 您已編輯虛擬主機、重新載入設定,但瀏覽器仍顯示 「Apache2 Ubuntu Default Page」 及其「It works!」橫幅。Apache 會提供第一個符合的虛擬主機;當沒有任何 ServerName 符合請求時,會採用按字母排序最前面的設定,000-default.conf 會排在 testapp.conf 之前。原因可能是請求的主機名稱不符合您的 ServerName,或您從未執行 sudo a2dissite 000-default。停用預設設定、執行 sudo systemctl reload apache2,再以 apache2ctl -S 確認;該命令會列出虛擬主機對應表,並顯示哪個設定擁有預設項目。也請清除瀏覽器快取;舊頁面的快取 200 回應可能會持續顯示。

.php 檔案被下載,而不是執行。 您開啟 info.php 時,瀏覽器下載了包含原始 <?php 原始碼的檔案,或將其顯示為純文字,而不是執行該檔案。這表示 Apache 將檔案當作靜態資源提供,原因可能是未掛載 PHP 處理程式、略過 sudo a2enmod proxy_fcgi、或 sudo a2enconf php8.3-fpm,也可能是之後未重新啟動 Apache。執行這 3 個步驟(步驟 4),然後重新載入頁面。使用 apache2ctl -M | grep fcgi 確認模組已載入;結果應列出 proxy_fcgi_module。這是原始碼外洩,不是單純的顯示問題,因此在將任何實際內容放到伺服器前,務必先修正。

ERROR 1698 (28000): Access denied for user 'root'@'localhost'。 您執行了 mysql -u root 或 mariadb -u root,但未加入 sudo。root 帳戶使用 unix_socket 驗證,因此只有在您的 OS 使用者確實是 root 時才會接受連線。修正方式是 sudo mysql,不需要 -u root,也不需要密碼。這則訊息表示 socket 驗證正常運作,不是安裝損壞。

應用程式顯示 ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1',但使用的密碼正確。該帳戶以 'appuser'@'localhost' 的形式存在,但應用程式透過 TCP 連線至 127.0.0.1;伺服器已停用主機名稱解析(skip-name-resolve),因此 MariaDB 會將兩者視為不同主機。localhost 是 Unix socket,127.0.0.1 是 TCP。將應用程式的主機指定為 localhost,讓它使用 socket 並符合該帳戶;如果該框架只能使用 TCP,則建立另一個帳戶 'appuser'@'127.0.0.1'。

/var/log/apache2/testapp-error.log 中出現 AH01071: Got error 'Primary script unknown',瀏覽器顯示 File not found.。Apache 已將請求交給 PHP-FPM,但 FPM 找不到 Apache 傳入的腳本路徑。常見原因有 2 個:設定中的 FPM socket 指向未安裝的 PHP 版本(版本升級後已改用 PHP 8.5,但仍設定為 8.3 socket),或檔案確實不存在,因為 DocumentRoot 與實際目錄不一致。使用 ls -l /run/php/ 檢查 socket 是否存在,確認 DocumentRoot 與檔案所在位置一致,然後重新啟動 php8.3-fpm(或實際安裝的 PHP-FPM 版本)及 apache2。

每次重新啟動時都會出現 AH00558: apache2: Could not reliably determine the server's fully qualified domain name。這是無害的警告,不是錯誤。Apache 只是告知尚未設定全域 ServerName。將 ServerName your.domain 寫入 /etc/apache2/conf-available/servername.conf,再執行 sudo a2enconf servername 即可消除警告。

Apache 啟動時出現 (98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80。另一個 Web 伺服器已佔用 port 80,常見情況是先前測試留下的 nginx 程序。使用 sudo ss -ltnp | grep :80 找出該程序,然後在啟動 Apache 前停止並停用其他服務。

FAQ

mod_php 與 PHP-FPM - 我該使用哪一個?

請使用 PHP-FPM。mod_php 會將直譯器嵌入每個 Apache 程序中,並強制使用效能較差的 prefork MPM,導致 Apache 在處理靜態圖片時仍需承擔 PHP 的額外開銷。PHP-FPM 將 PHP 作為獨立且可單獨調整的程序池執行,Apache 透過 socket 與其通訊,並可搭配效能更佳的執行緒化 event MPM 使用,未來若需遷移至 nginx 也無需修改程式碼。這是現代的預設配置;mod_php 僅適用於依賴特定程序內行為的舊有應用程式。

為什麼瀏覽器下載了 PHP 檔案而不是執行它?

Apache 將 .php 檔案視為靜態下載,因為沒有為其掛載 PHP 處理器。在 Ubuntu 24.04 搭配 FPM 的環境中,這代表您遺漏了 sudo a2enmod proxy_fcgi、sudo a2enconf php8.3-fpm 或後續的 Apache 重啟步驟。請執行這三項指令並重新載入,接著使用 apache2ctl -M | grep fcgi 確認 proxy_fcgi_module 已被列出。在修復此問題前,伺服器會洩漏原始碼,請將其視為緊急事項處理。

為什麼即使密碼正確,MariaDB 仍拒絕 root 存取?

因為沒有設定密碼,Ubuntu 的 MariaDB 預設透過 unix_socket 驗證 root 帳號,將其與作業系統的 root 使用者綁定。從一般 shell 執行 mysql -u root 會依設計回傳 ERROR 1698 (28000): Access denied for user 'root'@'localhost'。請改用 sudo mysql 連線,並為應用程式建立獨立的密碼驗證使用者,而非重複使用 root。

如何為我的 LAMP 網站加入 HTTPS?

安裝 certbot 與 python3-certbot-apache,將網域的 A 紀錄指向伺服器,然後執行 sudo certbot --apache。Apache 驗證器會透過您運作中的 Apache 驗證網域控制權,安裝程式會重寫 port 443 的虛擬主機設定並建立自動續約機制。完整的 Certbot 與 Apache 設定教學 涵蓋了驗證挑戰、續約計時器以及常見的失敗模式。

#lamp#apache#mariadb#php-fpm#ubuntu