LNMP 部署文档

环境信息

组件 版本
操作系统 openEuler 24.03
Nginx 1.24.0
PHP 8.3.30
MariaDB 10.5.29

安装策略:全部软件通过 dnf 从系统官方软件库安装,不涉及源码编译。
访问策略:仅开放 HTTPS(443),不对外提供明文服务。


一、环境准备

1.1 系统更新

dnf update -y

1.2 安装基础工具

dnf install -y curl wget vim net-tools bash-completion openssl

1.3 关闭 SELinux

生产环境建议保持开启并配置对应策略;测试/快速部署环境可临时关闭。

# 临时关闭
setenforce 0

# 永久关闭(需重启)
sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

1.4 配置防火墙

# 开放 HTTPS(443)
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

# 验证规则
firewall-cmd --list-all

二、安装 Nginx 1.24.0

2.1 安装

dnf install -y nginx

2.2 版本验证

nginx -v
# 预期输出:nginx version: nginx/1.24.0

2.3 启动并设为开机自启

systemctl start nginx
systemctl enable nginx
systemctl status nginx

2.4 核心配置文件路径

路径 说明
/etc/nginx/nginx.conf 主配置文件
/etc/nginx/conf.d/ 扩展配置目录,*.conf 自动加载
/usr/share/nginx/html/ 默认站点根目录
/var/log/nginx/ 日志目录
/etc/nginx/ssl/ 证书存放目录(手动创建)

2.5 基础优化配置

编辑 /etc/nginx/nginx.conf,在 http 块中调整说明:

http {
    # ===== 基础性能 =====
    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    # ===== 安全加固 =====
    server_tokens       off;                 # 隐藏 Nginx 版本号
    client_max_body_size    20m;
    client_body_buffer_size 128k;

    # ===== Gzip 压缩 =====
    gzip               on;
    gzip_vary          on;
    gzip_min_length    1k;
    gzip_comp_level    6;
    gzip_types         text/plain text/css text/xml
                       text/javascript application/javascript
                       application/json application/xml
                       image/svg+xml;
    gzip_disable       "MSIE [1-6]\.";

    # ===== 连接数限制(DDoS 防护) =====
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    limit_req_zone  $binary_remote_addr zone=req:10m rate=10r/s;

    include /etc/nginx/conf.d/*.conf;
}

系统中实际配置文件修改

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    #== 安全加固 ==
    server_tokens off;
    client_max_body_size 20m;
    client_body_buffer_size 128k;

    #== Gzip 压缩 ==
        gzip               on;
    gzip_vary          on;
    gzip_min_length    1k;
    gzip_comp_level    6;
    gzip_types         text/plain text/css text/xml
                       text/javascript application/javascript
                       application/json application/xml
                       image/svg+xml;
    gzip_disable       "MSIE [1-6]\.";

    
    # ===== 连接数限制(DDoS 防护) =====
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    limit_req_zone  $binary_remote_addr zone=req:10m rate=10r/s;



    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

#    server {
#        listen       80;
#        listen       [::]:80;
#        server_name  _;
#        root         /usr/share/nginx/html;
#
#        # Load configuration files for the default server block.
#        include /etc/nginx/default.d/*.conf;
#
#        error_page 404 /404.html;
#        location = /404.html {
#        }
#
#        error_page 500 502 503 504 /50x.html;
#        location = /50x.html {
#        }
#    }

# Settings for a TLS enabled server.
#
#    server {
#        listen       443 ssl http2;
#        listen       [::]:443 ssl http2;
#        server_name  _;
#        root         /usr/share/nginx/html;
#
#        ssl_certificate "/etc/pki/nginx/server.crt";
#        ssl_certificate_key "/etc/pki/nginx/private/server.key";
#        ssl_session_cache shared:SSL:1m;
#        ssl_session_timeout  10m;
#        ssl_ciphers PROFILE=SYSTEM;
#        ssl_prefer_server_ciphers on;
#
#        # Load configuration files for the default server block.
#        include /etc/nginx/default.d/*.conf;
#
#        error_page 404 /404.html;
#        location = /404.html {
#        }
#
#        error_page 500 502 503 504 /50x.html;
#        location = /50x.html {
#        }
#    }

}

三、安装 MariaDB 10.5.29

3.1 安装

dnf install -y mariadb-server mariadb

3.2 版本验证

mariadb --version
rpm -qa | grep mariadb-server
# 预期:mariadb-server-10.5.29

3.3 启动并设为开机自启

systemctl start mariadb
systemctl enable mariadb
systemctl status mariadb

3.4 安全初始化

mysql_secure_installation

交互式回答指南:

Enter current password for root (enter for none):    直接回车(初始无密码)
Switch to unix_socket authentication [Y/n]:           n
Change the root password? [Y/n]:                      Y
New password:                                         输入强密码
Re-enter new password:                                再次输入
Remove anonymous users? [Y/n]:                        Y
Disallow root login remotely? [Y/n]:                  Y
Remove test database and access to it? [Y/n]:         Y
Reload privilege tables now? [Y/n]:                   Y

3.5 创建应用数据库和用户

示例:

mysql -u root -p
-- 创建数据库
CREATE DATABASE app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 创建专用用户(仅本地连接)
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'YourStrongPassword123!';

-- 授权
GRANT ALL PRIVILEGES ON app_db.* TO 'app_user'@'localhost';

FLUSH PRIVILEGES;

SHOW DATABASES;
SELECT user, host FROM mysql.user;
EXIT;

具体配置一个测试库并写入测试数据:

-- 创建数据库
CREATE DATABASE test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 创建专用用户(仅可信地址连接)
CREATE USER 'test_user'@'192.168.171.%' IDENTIFIED BY 'KK********';
CREATE USER 'test_user'@'localhost' IDENTIFIED BY 'KK********';

-- 授权
GRANT ALL PRIVILEGES ON test_db.* TO 'test_user'@'192.168.171.%';
GRANT ALL PRIVILEGES ON test_db.* TO 'test_user'@'localhost';

FLUSH PRIVILEGES;

SHOW DATABASES;
SELECT user, host FROM mysql.user;
EXIT;

写入测试表:


-- ----------------------
-- Create Customers table
-- ----------------------
CREATE TABLE Customers
(
  cust_id      char(10)  NOT NULL ,
  cust_name    char(50)  NOT NULL ,
  cust_address char(50)  NULL ,
  cust_city    char(50)  NULL ,
  cust_state   char(5)   NULL ,
  cust_zip     char(10)  NULL ,
  cust_country char(50)  NULL ,
  cust_contact char(50)  NULL ,
  cust_email   char(255) NULL 
);

-- -----------------------
-- Create OrderItems table
-- -----------------------
CREATE TABLE OrderItems
(
  order_num  int          NOT NULL ,
  order_item int          NOT NULL ,
  prod_id    char(10)     NOT NULL ,
  quantity   int          NOT NULL ,
  item_price decimal(8,2) NOT NULL 
);


-- -------------------
-- Create Orders table
-- -------------------
CREATE TABLE Orders
(
  order_num  int      NOT NULL ,
  order_date datetime NOT NULL ,
  cust_id    char(10) NOT NULL 
);

-- ---------------------
-- Create Products table
-- ---------------------
CREATE TABLE Products
(
  prod_id    char(10)      NOT NULL ,
  vend_id    char(10)      NOT NULL ,
  prod_name  char(255)     NOT NULL ,
  prod_price decimal(8,2)  NOT NULL ,
  prod_desc  text          NULL 
);

-- --------------------
-- Create Vendors table
-- --------------------
CREATE TABLE Vendors
(
  vend_id      char(10) NOT NULL ,
  vend_name    char(50) NOT NULL ,
  vend_address char(50) NULL ,
  vend_city    char(50) NULL ,
  vend_state   char(5)  NULL ,
  vend_zip     char(10) NULL ,
  vend_country char(50) NULL 
);


-- -------------------
-- Define primary keys
-- -------------------
ALTER TABLE Customers ADD PRIMARY KEY (cust_id);
ALTER TABLE OrderItems ADD PRIMARY KEY (order_num, order_item);
ALTER TABLE Orders ADD PRIMARY KEY (order_num);
ALTER TABLE Products ADD PRIMARY KEY (prod_id);
ALTER TABLE Vendors ADD PRIMARY KEY (vend_id);


-- -------------------
-- Define foreign keys
-- -------------------
ALTER TABLE OrderItems ADD CONSTRAINT FK_OrderItems_Orders FOREIGN KEY (order_num) REFERENCES Orders (order_num);
ALTER TABLE OrderItems ADD CONSTRAINT FK_OrderItems_Products FOREIGN KEY (prod_id) REFERENCES Products (prod_id);
ALTER TABLE Orders ADD CONSTRAINT FK_Orders_Customers FOREIGN KEY (cust_id) REFERENCES Customers (cust_id);
ALTER TABLE Products ADD CONSTRAINT FK_Products_Vendors FOREIGN KEY (vend_id) REFERENCES Vendors (vend_id);

写入测试数据:


-- ------------------------
-- Populate Customers table
-- ------------------------
INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES('1000000001', 'Village Toys', '200 Maple Lane', 'Detroit', 'MI', '44444', 'USA', 'John Smith', 'sales@villagetoys.com');
INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES('1000000002', 'Kids Place', '333 South Lake Drive', 'Columbus', 'OH', '43333', 'USA', 'Michelle Green');
INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES('1000000003', 'Fun4All', '1 Sunny Place', 'Muncie', 'IN', '42222', 'USA', 'Jim Jones', 'jjones@fun4all.com');
INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES('1000000004', 'Fun4All', '829 Riverside Drive', 'Phoenix', 'AZ', '88888', 'USA', 'Denise L. Stephens', 'dstephens@fun4all.com');
INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES('1000000005', 'The Toy Store', '4545 53rd Street', 'Chicago', 'IL', '54545', 'USA', 'Kim Howard');

-- ----------------------
-- Populate Vendors table
-- ----------------------
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('BRS01','Bears R Us','123 Main Street','Bear Town','MI','44444', 'USA');
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('BRE02','Bear Emporium','500 Park Street','Anytown','OH','44333', 'USA');
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('DLL01','Doll House Inc.','555 High Street','Dollsville','CA','99999', 'USA');
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('FRB01','Furball Inc.','1000 5th Avenue','New York','NY','11111', 'USA');
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('FNG01','Fun and Games','42 Galaxy Road','London', NULL,'N16 6PS', 'England');
INSERT INTO Vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES('JTS01','Jouets et ours','1 Rue Amusement','Paris', NULL,'45678', 'France');

-- -----------------------
-- Populate Products table
-- -----------------------
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BR01', 'BRS01', '8 inch teddy bear', 5.99, '8 inch teddy bear, comes with cap and jacket');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BR02', 'BRS01', '12 inch teddy bear', 8.99, '12 inch teddy bear, comes with cap and jacket');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BR03', 'BRS01', '18 inch teddy bear', 11.99, '18 inch teddy bear, comes with cap and jacket');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BNBG01', 'DLL01', 'Fish bean bag toy', 3.49, 'Fish bean bag toy, complete with bean bag worms with which to feed it');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BNBG02', 'DLL01', 'Bird bean bag toy', 3.49, 'Bird bean bag toy, eggs are not included');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('BNBG03', 'DLL01', 'Rabbit bean bag toy', 3.49, 'Rabbit bean bag toy, comes with bean bag carrots');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('RGAN01', 'DLL01', 'Raggedy Ann', 4.99, '18 inch Raggedy Ann doll');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('RYL01', 'FNG01', 'King doll', 9.49, '12 inch king doll with royal garments and crown');
INSERT INTO Products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('RYL02', 'FNG01', 'Queen doll', 9.49, '12 inch queen doll with royal garments and crown');

-- ---------------------
-- Populate Orders table
-- ---------------------
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(20005, '2020-05-01', '1000000001');
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(20006, '2020-01-12', '1000000003');
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(20007, '2020-01-30', '1000000004');
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(20008, '2020-02-03', '1000000005');
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(20009, '2020-02-08', '1000000001');

-- -------------------------
-- Populate OrderItems table
-- -------------------------
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 1, 'BR01', 100, 5.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 2, 'BR03', 100, 10.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20006, 1, 'BR01', 20, 5.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20006, 2, 'BR02', 10, 8.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20006, 3, 'BR03', 10, 11.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 1, 'BR03', 50, 11.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 2, 'BNBG01', 100, 2.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 3, 'BNBG02', 100, 2.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 4, 'BNBG03', 100, 2.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 5, 'RGAN01', 50, 4.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 1, 'RGAN01', 5, 4.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 2, 'BR03', 5, 11.99);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 3, 'BNBG01', 10, 3.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 4, 'BNBG02', 10, 3.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 5, 'BNBG03', 10, 3.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 1, 'BNBG01', 250, 2.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 2, 'BNBG02', 250, 2.49);
INSERT INTO OrderItems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 3, 'BNBG03', 250, 2.49);

3.6 MariaDB 性能基础调优

编辑 /etc/my.cnf.d/mariadb-server.cnf,在 [mysqld] 段添加:

[mysqld]
character-set-server  = utf8mb4
collation-server      = utf8mb4_unicode_ci

innodb_buffer_pool_size = 512M
innodb_log_file_size = 128M
innodb_log_buffer_size = 16M

default-time_zone = '+8:00'

log_output = FILE
log_queries_not_using_indexes = 1

max_connections        = 200
max_connect_errors     = 100

query_cache_type       = 0

slow_query_log         = 1
slow_query_log_file    = /var/log/mariadb/slow.log
long_query_time        = 2
systemctl restart mariadb

查看日志目录并授权 mysql 用户读写:

mkdir -p /var/log/mariadb
chown mysql:mysql /var/log/mariadb

四、安装 PHP 8.3.30

4.1 搜索可用版本

# 列出所有php主包
dnf list available php
# 查看全部php相关包
dnf search php

4.2 安装 PHP 8.3 及常用扩展

dnf install -y php php-fpm

dnf install -y \
    php \
    php-fpm \
    php-cli \
    php-common \
    php-mysqlnd \
    php-pdo \
    php-gd \
    php-mbstring \
    php-xml \
    php-json \
    php-curl \
    php-zip \
    php-opcache \
    php-bcmath \
    php-intl \
    php-soap \
    php-ldap

4.3 版本验证

php -v
# 预期输出:PHP 8.3.30

4.4 配置 PHP-FPM

编辑 /etc/php-fpm.d/www.conf

user = nginx
group = nginx

; 推荐 Unix Socket(性能好,安全)配置时 listen.acl_users / listen.acl_groups 白名单(ACL 权限控制),文件属主、属组配置 listen.owner / listen.group 会警告。两种权限控制方式互斥,不能同时生效,
; 下面两行一定要注释,否则报ACL警告
;listen.acl_users =
;listen.acl_groups =

listen = /run/php-fpm/www.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660

编辑 /etc/php.ini

date.timezone = Asia/Shanghai
max_execution_time = 300
memory_limit = 256M
post_max_size = 20M
upload_max_filesize = 20M

expose_php = Off
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php-fpm/error.log

opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
opcache.fast_shutdown=1

session.cookie_httponly = 1	
session.cookie_secure = 1
session.use_strict_mode = 1
session.cookie_samesite = "Lax"

file_uploads = On
max_file_uploads = 10

disable_functions = exec,passthru,system,shell_exec,popen,proc_open

session.gc_maxlifetime = 1440
session.cookie_lifetime = 86400

错误写入日志,目录需创建并授权:

mkdir -p /var/log/php-fpm
chown nginx:nginx /var/log/php-fpm

4.5 启动 PHP-FPM

systemctl start php-fpm
systemctl enable php-fpm
systemctl status php-fpm

五、生成自签名 SSL 证书

5.1 创建证书目录

mkdir -p /etc/nginx/ssl
chmod 700 /etc/nginx/ssl

5.2 生成私钥与自签名证书

使用 openssl 一次性生成 RSA 2048 位私钥 + 有效期 3650 天(10 年)的自签名证书:

openssl req -x509 -nodes -newkey rsa:2048 \
    -keyout /etc/nginx/ssl/server.key \
    -out    /etc/nginx/ssl/server.crt \
    -days   3650 \
    -subj   "/C=CN/ST=Beijing/L=Beijing/O=TestCompany/OU=IT/CN=test_service"

参数说明

参数 说明
-x509 输出自签名证书(而非 CSR)
-nodes 私钥不加密口令(服务器自动启动不需要手动输密码)
-newkey rsa:2048 新建 RSA 2048 位密钥对
-days 3650 证书有效期 10 年
-subj 证书主题,CN 填写服务器 IP 或域名
字段 全称 含义 示例
/C=CN Country 国家二字代码,中国固定 CN C=CN
/ST= State/Province 省份 / 直辖市 ST=Beijing
/L= Locality 城市 L=Beijing
/O= Organization 公司 / 组织名称 O=TestCompany
/OU= Organizational Unit 部门 OU=IT
/CN= Common Name 核心字段,服务器域名 / IP CN=192.168.171.10 或 CN=web.test.com

5.3 生成 DH 参数(增强前向安全性)

openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048

此步骤会消耗约 1~3 分钟 CPU,请耐心等待。

5.4 设置证书文件权限

# 私钥仅 root 可读
chmod 600 /etc/nginx/ssl/server.key
chmod 644 /etc/nginx/ssl/server.crt
chmod 644 /etc/nginx/ssl/dhparam.pem

# 验证
ls -la /etc/nginx/ssl/

六、Nginx HTTPS 站点配置

6.1 删除或归档旧配置

# 若 conf.d 下存在默认 HTTP 配置,先备份
mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak 2>/dev/null || true

6.2 创建 HTTPS 站点配置

新建 /etc/nginx/conf.d/lnmp-ssl.conf

# ============================================================
# Server Block 1 — HTTP 监听(仅做 301 跳转,不提供实际内容),注释掉不适用http
# ============================================================
#server {
#    listen      80;
#    server_name 192.168.171.188;
#
#    # 所有 HTTP 请求 301 永久重定向到 HTTPS
#    return 301 https://$host$request_uri;
#}

# ============================================================
# Server Block 2 — HTTPS 主站点
# ============================================================
server {
    listen      443 ssl;
    server_name 192.168.177.188;                              # 替换为实际域名或 IP
    root        /usr/share/nginx/html;
    index       index.php index.html index.htm;

    # ===== 日志 =====
    access_log  /var/log/nginx/lnmp-ssl-access.log;
    error_log   /var/log/nginx/lnmp-ssl-error.log;

    # ===== SSL 证书 =====
    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    # ===== SSL 协议与加密套件 =====
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # ===== DH 参数 =====
    ssl_dhparam         /etc/nginx/ssl/dhparam.pem;

    # ===== SSL Session 缓存(提升性能) =====
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # ===== OCSP Stapling(自签名证书无效,注释保留) =====
    # ssl_stapling on;
    # ssl_stapling_verify on;

    # ===== 安全响应头 =====
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;  # HSTS
    add_header X-Content-Type-Options    "nosniff" always;
    add_header X-Frame-Options           "SAMEORIGIN" always;
    add_header X-XSS-Protection          "1; mode=block" always;
    add_header Referrer-Policy           "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy   "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;

    # ===== PHP 处理 =====
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass   unix:/run/php-fpm/www.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;

        fastcgi_read_timeout     300;
        fastcgi_send_timeout     300;
        fastcgi_connect_timeout  60;

        fastcgi_buffers     16 16k;
        fastcgi_buffer_size 32k;
    }

    # ===== 禁止访问隐藏文件 =====
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }

    # ===== 静态资源缓存 =====
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires     30d;
        add_header  Cache-Control "public, immutable";
        access_log  off;
    }

    # ===== 限制请求方法 =====
    if ($request_method !~ ^(GET|HEAD|POST)$) {
        return 405;
    }
}

6.3 验证并重载 Nginx

nginx -t
# 预期:syntax is ok / test is successful

systemctl reload nginx

6.4 确认端口监听

ss -tlnp | grep -E ':(80|443)'
# 预期:
# LISTEN  0  ...  0.0.0.0:80   ...  nginx
# LISTEN  0  ...  0.0.0.0:443  ...  nginx

七、集成测试

7.1 浏览器验证 HTTPS

访问 https://<服务器IP>

  • 浏览器会显示"证书不受信任"警告(自签名证书正常现象),点击"继续访问"
  • 应看到 Nginx 默认欢迎页
  • 地址栏显示 🔒 图标(证书信息)

7.2 curl 命令行验证

# 测试 HTTPS(-k 忽略自签名证书验证)
curl -kv https://127.0.0.1 2>&1 | grep -E '(SSL|TLS|issuer|subject|HTTP)'

# 测试 HTTP → HTTPS 跳转
curl -v http://127.0.0.1 2>&1 | grep -E '(Location|HTTP)'
# 预期:< HTTP/1.1 301 Moved Permanently
#       < Location: https://127.0.0.1/

7.3 创建 PHP 探针页(测试后立即删除)

cat > /usr/share/nginx/html/info.php << 'EOF'
<?php phpinfo(); ?>
EOF
chown nginx:nginx /usr/share/nginx/html/info.php

浏览器访问 https://<服务器IP>/info.php,确认:

  • PHP Version 显示 8.3.30
  • Server API 显示 FPM/FastCGI
  • $_SERVER['HTTPS'] 值为 on

7.4 数据库连接测试

cat > /usr/share/nginx/html/dbtest.php << 'EOF'
<?php
$host   = '192.168.171.1';
$dbname = 'test_db';
$user   = 'test_user';
$pass   = 'KK********';

try {
    $pdo = new PDO(
        "mysql:host=$host;dbname=$dbname;charset=utf8mb4",
        $user,
        $pass,
        [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ]
    );

    $stmt = $pdo->query("SELECT VERSION() AS ver");
    $row  = $stmt->fetch();

    echo "<h2>LNMP 环境连接成功!</h2>";
    echo "<p>MariaDB 版本: " . htmlspecialchars($row['ver']) . "</p>";
    echo "<p>PHP 版本: " . phpversion() . "</p>";
    echo "<p>HTTPS: " . (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? '已启用 ✓' : '未启用') . "</p>";
} catch (PDOException $e) {
    http_response_code(500);
    echo "<h2>连接失败</h2>";
    echo "<p>错误: " . htmlspecialchars($e->getMessage()) . "</p>";
}
EOF
chown nginx:nginx /usr/share/nginx/html/dbtest.php

访问 https://<服务器IP>/dbtest.php

7.5 测试完成后清理

rm -f /usr/share/nginx/html/info.php
rm -f /usr/share/nginx/html/dbtest.php

安全警告phpinfo() 会暴露大量服务器信息,生产环境严禁保留。


八、安全加固检查清单

序号 检查项 命令/方法
1 删除测试文件 ls /usr/share/nginx/html/*.php
2 确认 expose_php = Off grep expose_php /etc/php.ini
3 确认 display_errors = Off grep display_errors /etc/php.ini
4 确认 server_tokens off grep server_tokens /etc/nginx/nginx.conf
5 确认 HTTP 强制跳转 HTTPS(本文档不涉及,http已关闭) curl -v http://127.0.0.1 | grep Location
6 确认 HSTS 响应头 curl -kI https://127.0.0.1 | grep Strict
7 私钥权限 600 ls -la /etc/nginx/ssl/server.key
8 MariaDB root 仅本地登录 SELECT user,host FROM mysql.user WHERE user='root';
9 PHP-FPM 以 nginx 用户运行 ps aux | grep php-fpm
10 TLS 协议不低于 1.2 curl -kv --tls-max 1.1 https://127.0.0.1(应连接失败)
11 防火墙只允许指定主机登录 firewall-cmd --add-rich-rule='rule family=ipv4 source address=192.168.171.0/24 port port=3306 protocol=tcp accept' --permanent

8.1 目录权限建议

# 站点根目录:root 所有,nginx 只读
chown -R root:root  /usr/share/nginx/html/
chmod -R 755        /usr/share/nginx/html/

# 需写入的目录(上传目录、缓存目录)
mkdir -p /usr/share/nginx/html/upload
chown -R nginx:nginx /usr/share/nginx/html/upload
chmod -R 755        /usr/share/nginx/html/upload

九、常用运维命令速查

# ===== 服务管理 =====
systemctl {start|stop|restart|reload|status} nginx
systemctl {start|stop|restart|status}        php-fpm
systemctl {start|stop|restart|status}        mariadb

# ===== Nginx =====
nginx -t                                  # 配置语法检查
nginx -s reload                           # 平滑重载
tail -f /var/log/nginx/lnmp-ssl-error.log # 实时错误日志

# ===== SSL 证书检查 =====
openssl x509 -in /etc/nginx/ssl/server.crt -noout -text | grep -E '(Subject|Issuer|Not Before|Not After)'
# 查看证书到期时间
openssl x509 -in /etc/nginx/ssl/server.crt -noout -enddate

# ===== MariaDB =====
mysql -u root -p
#备份
mysqldump -u root -p app_db > backup.sql

# ===== PHP =====
php -v                                    #查看版本
php -m                                    # 列出已安装模块
tail -f /var/log/php-fpm/error.log        #查看错误错误日志

# ===== 端口监听检查 =====
ss -tlnp | grep -E ':(80|443|3306)'

十、常见问题排错

10.1 Nginx 报 502 Bad Gateway

systemctl status php-fpm
ls -la /run/php-fpm/www.sock
tail -50 /var/log/php-fpm/error.log
tail -50 /var/log/nginx/lnmp-ssl-error.log

10.2 浏览器显示"SSL_ERROR_RX_RECORD_TOO_LONG"

原因:浏览器通过 HTTPS 访问时,服务器实际返回了 HTTP 内容。

排查

# 确认 443 端口的 server block 有 ssl 指令
grep -n 'ssl' /etc/nginx/conf.d/lnmp-ssl.conf

# 确认证书文件存在且路径正确
ls -la /etc/nginx/ssl/
nginx -t

10.3 HTTP 没有跳转到 HTTPS(本文档不涉及,已经禁用http)

# 确认 80 端口的 server block 存在
grep -A5 'listen.*80' /etc/nginx/conf.d/lnmp-ssl.conf

# 确认 Nginx 已重载
systemctl reload nginx

# 测试
curl -v http://127.0.0.1

10.4 证书警告与信任(自签名场景)

自签名证书在所有浏览器中均会显示不受信任警告,这是正常现象:

  • Chrome/Edge:点击"高级" → “继续前往”
  • Firefox:点击"接受风险并继续"
  • curl:使用 -k--insecure 参数跳过验证
  • 将证书导入系统/浏览器信任库(适用于内网测试环境):
# 将证书复制到系统 CA 目录并更新信任库(openEuler)
cp /etc/nginx/ssl/server.crt /etc/pki/ca-trust/source/anchors/lnmp-self-signed.crt
update-ca-trust

10.5 PHP 页面空白或报 500 错误

# 临时启用错误显示(仅排查用!)
sed -i 's/display_errors = Off/display_errors = On/' /etc/php.ini
systemctl restart php-fpm
# 排查完毕后务必改回 Off

10.6 上传文件被截断

确保以下三项一致:

grep -E '(upload_max_filesize|post_max_size)' /etc/php.ini
grep client_max_body_size /etc/nginx/nginx.conf
# 要求:client_max_body_size >= post_max_size >= upload_max_filesize

十一、快速安装脚本(附录)

保存为 install_lnmp.sh,执行前确认变量值:

#!/bin/bash
set -e

# =============================================
# LNMP 一键部署脚本(HTTPS 自签名)
# OS: openEuler 24.03
# 组件: Nginx 1.24.0 / PHP 8.3 / MariaDB 10.5
# =============================================

# ---------- 可修改变量 ----------
CERT_SUBJ="/C=CN/ST=Province/L=City/O=MyOrg/OU=IT"
CERT_DAYS=3650
SSL_DIR="/etc/nginx/ssl"
# --------------------------------

echo "===== Step 1: 系统更新 ====="
dnf update -y
dnf install -y openssl

echo "===== Step 2: 安装 Nginx ====="
dnf install -y nginx
systemctl start nginx && systemctl enable nginx
nginx -v

echo "===== Step 3: 安装 MariaDB ====="
dnf install -y mariadb-server mariadb
systemctl start mariadb && systemctl enable mariadb

echo "===== Step 4: 安装 PHP 8.3 ====="
dnf install -y \
    php php-fpm php-cli php-common \
    php-mysqlnd php-pdo php-gd php-mbstring \
    php-xml php-json php-curl php-zip \
    php-opcache php-bcmath php-intl php-soap php-ldap

php -v

echo "===== Step 5: 配置 PHP-FPM ====="
sed -i 's/^user = apache/user = nginx/'    /etc/php-fpm.d/www.conf
sed -i 's/^group = apache/group = nginx/'  /etc/php-fpm.d/www.conf
sed -i 's|^listen = 127.0.0.1:9000|listen = /run/php-fpm/www.sock|' /etc/php-fpm.d/www.conf
sed -i 's/^;listen.owner = nobody/listen.owner = nginx/'  /etc/php-fpm.d/www.conf
sed -i 's/^;listen.group = nobody/listen.group = nginx/'  /etc/php-fpm.d/www.conf
sed -i 's/^;listen.mode = 0660/listen.mode = 0660/'       /etc/php-fpm.d/www.conf

systemctl start php-fpm && systemctl enable php-fpm

echo "===== Step 6: 生成自签名证书 ====="
mkdir -p "$SSL_DIR"
chmod 700 "$SSL_DIR"

openssl req -x509 -nodes -newkey rsa:2048 \
    -keyout "${SSL_DIR}/server.key" \
    -out    "${SSL_DIR}/server.crt" \
    -days   "$CERT_DAYS" \
    -subj   "$CERT_SUBJ"

echo "正在生成 DH 参数(需要约 1~3 分钟)..."
openssl dhparam -out "${SSL_DIR}/dhparam.pem" 2048

chmod 600 "${SSL_DIR}/server.key"
chmod 644 "${SSL_DIR}/server.crt"
chmod 644 "${SSL_DIR}/dhparam.pem"

echo "===== Step 7: 配置 Nginx HTTPS 站点 ====="
cat > /etc/nginx/conf.d/lnmp-ssl.conf << 'NGINXEOF'
server {
    listen      80;
    server_name _;
    return 301 https://$host$request_uri;
}

server {
    listen      443 ssl;
    server_name _;
    root        /usr/share/nginx/html;
    index       index.php index.html index.htm;

    access_log  /var/log/nginx/lnmp-ssl-access.log;
    error_log   /var/log/nginx/lnmp-ssl-error.log;

    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_dhparam         /etc/nginx/ssl/dhparam.pem;

    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options    "nosniff" always;
    add_header X-Frame-Options           "SAMEORIGIN" always;
    add_header X-XSS-Protection          "1; mode=block" always;
    add_header Referrer-Policy           "strict-origin-when-cross-origin" always;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass   unix:/run/php-fpm/www.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
        fastcgi_read_timeout 300;
    }

    location ~ /\. {
        deny all;
    }
}
NGINXEOF

nginx -t && systemctl reload nginx

echo "===== Step 8: 配置防火墙 ====="
firewall-cmd --permanent --add-service=http  2>/dev/null || true
firewall-cmd --permanent --add-service=https 2>/dev/null || true
firewall-cmd --add-rich-rule='rule family=ipv4 source address=192.168.171.0/24 port port=3306 protocol=tcp accept' --permanent 2>/dev/null || true
firewall-cmd --reload 2>/dev/null || true

echo ""
echo "===== 部署完成 ====="
echo "Nginx:    $(nginx -v 2>&1)"
echo "MariaDB:  $(rpm -q mariadb-server)"
echo "PHP:      $(php -v 2>&1 | head -1)"
echo ""
echo "证书信息:"
openssl x509 -in "${SSL_DIR}/server.crt" -noout -subject -enddate
echo ""
echo "后续步骤:"
echo "  1. 运行 mysql_secure_installation 完成 MariaDB 安全初始化"
echo "  2. 访问 https://<服务器IP> 验证(浏览器会显示自签名证书警告,属正常)"
echo "  3. 访问 http://<服务器IP> 确认自动跳转到 HTTPS"

执行方式:

chmod +x install_lnmp.sh

# 修改脚本中的 CERT_SUBJ 变量,将 CN 替换为实际 IP 或域名,再执行:
./install_lnmp.sh

文档版本: v1.1
适用范围: openEuler 24.03 + Nginx 1.24.0 + PHP 8.3.30 + MariaDB 10.5.29
安装方式: dnf 系统仓库安装
访问协议: 仅 HTTPS(自签名证书),HTTP 强制 301 跳转

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐