使用 PHP 撰寫 CLI 腳本管理 Nginx 虛擬主機,優勢在於:
vhost-manager/
├── bin/
│ └── vhost # 入口執行檔 (shebang #!/usr/bin/env php)
├── src/
│ ├── Command/
│ │ ├── CreateCommand.php
│ │ ├── DeleteCommand.php
│ │ └── ListCommand.php
│ ├── Service/
│ │ ├── NginxConfigService.php
│ │ └── FileSystemService.php
│ └── Template/
│ └── vhost.conf.tpl # Nginx 設定模板
├── composer.json
└── phpunit.xml
templates/vhost.conf.tpl
server {
listen 80;
listen [::]:80;
server_name {$domain};
root {$rootPath};
index index.php index.html index.htm;
access_log /var/log/nginx/{$domain}_access.log;
error_log /var/log/nginx/{$domain}_error.log;
# 安全性:隱藏版本號、限制請求大小
server_tokens off;
client_max_body_size 20M;
# 忽略 favicon/robots 記錄
location = /favicon.ico {
access_log off; log_not_found off;
}
location = /robots.txt {
access_log off; log_not_found off; allow all;
}
# PHP 處理
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_intercept_errors on;
}
# 靜態資源快取
location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
# 禁止存取隱藏檔、備份檔
location ~ /\. {
deny all;
}
location ~* \.(?:bak|backup|sql|log)$ {
deny all;
}
}
<?php
// src/Service/NginxConfigService.php
declare(strict_types=1);
namespace VhostManager\Service;
class NginxConfigService
{
private string $sitesAvailableDir;
private string $sitesEnabledDir;
private string $templatePath;
private string $webRootBase;
public function __construct(
string $sitesAvailableDir = '/etc/nginx/sites-available',
string $sitesEnabledDir = '/etc/nginx/sites-enabled',
string $templatePath = __DIR__ . '/../../templates/vhost.conf.tpl',
string $webRootBase = '/var/www'
) {
$this->sitesAvailableDir = $sitesAvailableDir;
$this->sitesEnabledDir = $sitesEnabledDir;
$this->templatePath = $templatePath;
$this->webRootBase = $webRootBase;
}
public function create(string $domain): bool
{
$this->validateDomain($domain);
$configFile = "{$this->sitesAvailableDir}/{$domain}.conf";
$linkFile = "{$this->sitesEnabledDir}/{$domain}.conf";
$rootPath = "{$this->webRootBase}/{$domain}";
// 1. 檢查是否已存在
if (file_exists($configFile) || is_link($linkFile)) {
throw new \RuntimeException("虛擬主機 {$domain} 已存在");
}
// 2. 建立網站根目錄
if (!is_dir($rootPath)) {
mkdir($rootPath, 0755, true);
// 建立預設 index.php
file_put_contents("{$rootPath}/index.php", "templatePath);
$config = strtr($template, [
'{$domain}' => $domain,
'{$rootPath}' => $rootPath,
]);
// 4. 寫入設定檔
file_put_contents($configFile, $config);
// 5. 建立軟連結啟用
symlink($configFile, $linkFile);
// 6. 測試 Nginx 設定並重載
$this->reloadNginx();
return true;
}
public function delete(string $domain): bool
{
$configFile = "{$this->sitesAvailableDir}/{$domain}.conf";
$linkFile = "{$this->sitesEnabledDir}/{$domain}.conf";
$rootPath = "{$this->webRootBase}/{$domain}";
if (!file_exists($configFile)) {
throw new \RuntimeException("虛擬主機 {$domain} 不存在");
}
// 1. 移除啟用連結
if (is_link($linkFile)) {
unlink($linkFile);
}
// 2. 備份設定檔
$backupDir = "/etc/nginx/backup";
if (!is_dir($backupDir)) {
mkdir($backupDir, 0755, true);
}
$backupFile = "{$backupDir}/{$domain}.conf." . date('YmdHis');
rename($configFile, $backupFile);
// 3. 封存並移除網站目錄
$archive = "/var/backups/{$domain}.tar.gz";
exec("tar -czf {$archive} {$rootPath} 2>/dev/null");
exec("rm -rf {$rootPath}");
// 4. 重載 Nginx
$this->reloadNginx();
return true;
}
public function list(): array
{
$files = glob("{$this->sitesAvailableDir}/*.conf");
$domains = [];
foreach ($files as $file) {
$domain = basename($file, '.conf');
$enabled = is_link("{$this->sitesEnabledDir}/{$domain}.conf");
$domains[] = ['domain' => $domain, 'enabled' => $enabled];
}
return $domains;
}
private function validateDomain(string $domain): void
{
if (!preg_match('/^([a-z0-9-]+\.)+[a-z]{2,}$/i', $domain)) {
throw new \InvalidArgumentException("無效域名格式: {$domain}");
}
}
private function reloadNginx(): void
{
// 測試設定語法
$test = exec('nginx -t 2>&1', $output, $returnCode);
if ($returnCode !== 0) {
throw new \RuntimeException("Nginx 設定測試失敗: " . implode("\n", $output));
}
// 重載 (無中斷)
exec('systemctl reload nginx 2>&1', $output, $returnCode);
if ($returnCode !== 0) {
// 嘗試 service 指令
exec('service nginx reload 2>&1', $output, $returnCode);
if ($returnCode !== 0) {
throw new \RuntimeException("Nginx 重載失敗: " . implode("\n", $output));
}
}
}
}
?>
<?php
// bin/vhost
#!/usr/bin/env php
add(new CreateCommand());
$app->add(new DeleteCommand());
$app->add(new ListCommand());
$app->run();
?>
<?php
// src/Command/CreateCommand.php
namespace VhostManager\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use VhostManager\Service\NginxConfigService;
class CreateCommand extends Command
{
private NginxConfigService $service;
public function __construct(NginxConfigService $service)
{
$this->service = $service;
parent::__construct();
}
protected function configure(): void
{
$this->setName('create')
->setDescription('建立新的虛擬主機')
->addArgument('domain', InputArgument::REQUIRED, '域名 (例: example.com)');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$domain = $input->getArgument('domain');
try {
$this->service->create($domain);
$output->writeln("虛擬主機 {$domain} 建立成功 ");
return Command::SUCCESS;
} catch (\Throwable $e) {
$output->writeln("{$e->getMessage()} ");
return Command::FAILURE;
}
}
}
?>
# 安裝依賴
composer require symfony/console symfony/filesystem
# 設定執行權限
chmod +x bin/vhost
# 建立虛擬主機
./bin/vhost create example.com
# 列出所有虛擬主機
./bin/vhost list
# 刪除虛擬主機
./bin/vhost delete example.com
create 時自動申請並設定 HTTPS。proxy_pass 至上游應用 (Node.js, Go, Java)。/etc/nginx,每次變更自動 commit。sudoers 僅允許 nginx -t、systemctl reload nginx、tar、rm 特定路徑。使用 PHP 開發 Nginx 虛擬主機管理工具,結合模板引擎、Symfony Console、服務層封裝,能構建出結構清晰、可測試、可擴展的自動化運維工具。關鍵在於:模板化設定、原子性操作 (測試→寫入→重載)、完善錯誤處理與安全審計。