岁月

A JOURNAL OF TRAVEL · CODE · MARKETS
第三十六期 · 第 36 号 二〇二六年八月 · 立秋之后

openSUSE Leap 16.0 + NGINX 应用服务器搭建指南

2026-07-24 8 阅读

一台闲置的旧电脑,装上 openSUSE,跑起 NGINX——从静态资源托管到反向代理微服务,再到 JDK 部署、systemd 托管与 HTTPS,一条命令一条命令,把它变成一台开箱即用的应用服务器。

  • 日期:2026-07-23
  • 分类:技术 · 运维
  • 标签:#NGINX #openSUSE #反向代理 #运维 #部署 #Linux

这台旧电脑,终于能重新上岗了。让它当一台应用服务器,是件特别合适的事:外层的 NGINX 一手托起前端的静态资源,一手指向 Spring Boot 后端做反向代理,再往下是 PostgreSQL、JDK、systemd,一路铺到底。这篇指南把整条路完整记录下来——从部署架构到 SSH,从软件源到装 NGINX、起服务、开防火墙,再到配置静态资源、反向代理、JDK、systemd 与 HTTPS,一步步可复现。

全文以命令为主线,尽量把每一步的"为什么"也讲清楚。照着走,你的旧电脑也能成为一台干净、可控、随时能重启恢复的应用服务器。


部署架构概览

整台服务器的核心是一层清晰的"前置一个入口"架构。浏览器只跟 NGINX(80 端口)打交道,NGINX 再把请求分流给前端静态文件或后端的 Spring Boot 服务。

                    ┌──────────────────────────────────────────┐
                    │           openSUSE Leap 16.0              │
                    │            (旧电脑应用服务器)               │
浏览器请求 ────────▶  │                                          │
http://服务器IP       │   ┌──────────┐    ┌───────────────────┐  │
                    │   │  NGINX   │───▶│   前端静态文件      │  │
                    │   │  :80     │    │  /srv/www/...     │  │
                    │   └────┬─────┘    └───────────────────┘  │
                    │        │ /api/*                         │
                    │        ▼                                │
                    │   ┌──────────────┐    ┌───────────────┐  │
                    │   │  proxy_pass  │───▶│  Spring Boot  │  │
                    │   │  → :8081     │    │  user-service │  │
                    │   └──────────────┘    │  (JAR + JDK)  │  │
                    │                       └───────┬───────┘  │
                    │                               │ JDBC     │
                    │                               ▼          │
                    │                       ┌───────────────┐  │
                    │                       │  PostgreSQL   │  │
                    │                       │   :5432       │  │
                    │                       └───────────────┘  │
                    └──────────────────────────────────────────┘

数据流向

请求路径 NGINX 处理方式 目标
/(首页、页面路由) 返回静态文件 /srv/www/frontend-webapp/dist/index.html
/css/*/js/*/img/* 返回静态文件 + 缓存 /srv/www/frontend-webapp/dist/
/api/users/* 反向代理 http://localhost:8081/api/users/*
/api/*(其他 API) 反向代理 http://localhost:8081/api/*

架构说明:NGINX 作为唯一对外入口(端口 80),同时承担两个职责——静态资源服务(托管前端 HTML/CSS/JS)和反向代理(将 API 请求转发到 Spring Boot 后端的 8081 端口)。Spring Boot 服务不直接暴露,外部无法直接访问 8081 端口。


SSH 远程登录配置

系统安装完成后,第一步是配置 SSH 远程登录,以便从其他电脑(如你的 Mac)通过终端操作服务器。需要完成两项配置:允许 root 用户 SSH 登录为普通用户授予 sudo 权限。完成这两步后,即可从开发机远程操作服务器,后续步骤均可通过 SSH 执行。

为什么需要这两步? openSUSE Leap 16.0 出于安全考虑,默认禁止 root 用户通过 SSH 登录,且安装时创建的普通用户默认没有 sudo 权限。这意味着你只能通过服务器本机登录 root,且普通用户无法执行特权命令(如 zypper 安装软件)。

2.1 允许 root 用户 SSH 登录

# 1. 确认 SSH 服务已安装并运行(Server 角色通常已默认安装)
systemctl status sshd

# 如果未安装,手动安装:
# zypper install openssh
# systemctl enable --now sshd

# 2. 编辑 SSH 配置文件,允许 root 登录
sudo vi /etc/ssh/sshd_config

# 3. 找到 PermitRootLogin 行,取消注释并修改为:
PermitRootLogin yes

# 4. 保存退出后,重启 SSH 服务使配置生效
sudo systemctl restart sshd

2.2 为普通用户授予 sudo 权限

openSUSE 使用 wheel 组管理 sudo 权限。将安装时创建的普通用户加入 wheel 组即可获得管理员权限。

# 将普通用户加入 wheel 组(将 用户名 替换为你的用户名)
usermod -aG wheel 用户名

# 验证用户已加入 wheel 组
groups 用户名
# 预期输出: 用户名 wheel

# 验证 sudo 权限(切换到普通用户测试)
su - 用户名
sudo whoami
# 输入密码后,预期输出: root

2.3 开放防火墙 SSH 端口

# 开放防火墙 SSH 端口
sudo firewall-cmd --add-service=ssh --permanent
sudo firewall-cmd --reload

# 验证
sudo firewall-cmd --list-all

2.4 从开发机测试远程登录

# 使用普通用户登录
ssh 用户名@192.168.1.100

# 使用 root 用户登录
ssh root@192.168.1.100

# 登录后测试 sudo 权限(普通用户)
sudo zypper refresh

SSH 配置完成:现在可以从开发机远程操作服务器了。后续所有步骤均可通过 SSH 远程执行,无需在服务器本机操作。

⚠️ 安全提醒:允许 root 直接 SSH 登录仅适用于测试环境。生产环境建议仅使用普通用户 + sudo 方式登录,将 PermitRootLogin 设为 noprohibit-password(仅允许密钥登录)。


更换软件源为清华镜像

openSUSE Leap 16.0 默认使用官方源(cdn.opensuse.org),国内访问速度慢甚至超时。安装软件前务必先切换为国内镜像

⚠️ Leap 16.0 软件源结构变化(重要!):与 Leap 15.x 不同,Leap 16.0 移除了 update、backports、sle 三个仓库,只保留 oss 和 non-oss 两个主仓库。更新包已合并到主仓库中。如果按照旧教程添加 update/leap/16.0/oss/ 源,会报 Repository type can't be determined 错误。

3.1 移除 RIS 服务

Leap 16.0 默认使用 RIS(Repository Index Service)自动管理软件源,会覆盖手动修改。需要先移除:

# 查看当前 RIS 服务
sudo zypper ls

# 移除 openSUSE RIS 服务(会自动清理 openSUSE: 前缀的源)
sudo zypper rs openSUSE

# 如果提示找不到服务,直接卸载 RIS 包
sudo zypper remove openSUSE-repos-Leap

3.2 清理残留源并添加清华镜像

# 1. 查看当前所有软件源
sudo zypper lr

# 2. 删除所有旧源(逐个删除,按编号或别名)
# 如果源很多,可以一次性全部删除:
sudo zypper rr --all

# 3. 添加清华镜像源(Leap 16.0 只有这两个源)
sudo zypper ar -p 98 -f 'https://mirrors.tuna.tsinghua.edu.cn/opensuse/distribution/leap/16.0/repo/oss/' repo-oss
sudo zypper ar -p 98 -f 'https://mirrors.tuna.tsinghua.edu.cn/opensuse/distribution/leap/16.0/repo/non-oss/' repo-non-oss

# 4. 清除旧缓存并刷新
sudo zypper clean --all
sudo zypper ref

3.3 验证软件源

# 确认只剩两个清华镜像源
sudo zypper lr

# 预期输出:
# # | Alias        | Name         | Enabled | GPG Check | Refresh
# --+--------------+--------------+---------+-----------+--------
# 1 | repo-non-oss | repo-non-oss | Yes     | (r) Yes   | Yes
# 2 | repo-oss     | repo-oss     | Yes     | (r) Yes   | Yes

软件源配置完成:如果 zypper ref 没有报错,说明镜像源配置成功。后续所有 zypper install 命令都会从清华镜像下载,速度大幅提升。

⚠️ 常见问题:Repository type can't be determined:如果 zypper ref 报此错误,说明存在指向不存在路径的源。用 sudo zypper lr 检查,删除所有指向 update/leap/16.0/ 的源,只保留上面添加的两个即可。


安装 NGINX

openSUSE Leap 16.0 默认仓库包含 NGINX,直接安装:

# 刷新软件源
sudo zypper refresh

# 安装 NGINX
sudo zypper install nginx

# 验证安装
nginx -v
# 预期输出: nginx version: nginx/1.x.x

# 查看安装的文件位置
rpm -ql nginx | head -20

安装路径一览

用途 路径
主配置文件 /etc/nginx/nginx.conf
子配置目录 /etc/nginx/conf.d/*.conf
MIME 类型 /etc/nginx/mime.types
默认站点根目录 /srv/www/htdocs/
访问日志 /var/log/nginx/access.log
错误日志 /var/log/nginx/error.log
可执行文件 /usr/sbin/nginx
NGINX 运行用户 nginx(安装时自动创建)

openSUSE 与 Debian 的区别:openSUSE 不使用 Debian/Ubuntu 的 sites-available / sites-enabled 目录结构,而是统一使用 /etc/nginx/conf.d/ 目录,每个 .conf 文件代表一个站点配置。主配置文件 nginx.conf 的 http 块末尾已包含 include /etc/nginx/conf.d/*.conf;


启动服务与开机自启

# 启动 NGINX
systemctl start nginx

# 设置开机自启
systemctl enable nginx

# 一步到位:启动并设为开机自启
# systemctl enable --now nginx

# 查看运行状态
systemctl status nginx

验证 NGINX 是否正常运行:

# 检查 80 端口是否监听
ss -tlnp | grep :80
# 预期: LISTEN  0  511  0.0.0.0:80  0.0.0.0:*  users:(("nginx",...))

# 本机访问测试
curl -I http://localhost
# 预期: HTTP/1.1 200 OK

服务启动成功:如果 systemctl status nginx 显示 active (running),且 curl -I http://localhost 返回 200,说明 NGINX 已正常运行。


防火墙配置

openSUSE Leap 16.0 默认使用 firewalld。需要开放 HTTP(80) 和 HTTPS(443) 端口:

# 确认防火墙已运行
systemctl enable --now firewalld

# 开放 HTTP 和 HTTPS 服务(推荐方式)
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https

# 重载防火墙使规则生效
firewall-cmd --reload

# 验证已开放的服务
firewall-cmd --list-all
# 在你的 Mac 上测试端口可达
curl -I http://192.168.1.100
# 预期: HTTP/1.1 200 OK

nc -zv 192.168.1.100 80
# 预期: Connection succeeded!

NGINX 配置文件结构

主配置文件 nginx.conf

查看默认主配置的关键部分:

cat /etc/nginx/nginx.conf
user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

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

    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;
    keepalive_timeout  65;

    # 加载 conf.d 目录下所有站点配置
    include /etc/nginx/conf.d/*.conf;
}

配置原则nginx.conf 保留默认全局设置(worker 进程、日志格式、gzip 等),站点特定配置全部放在 /etc/nginx/conf.d/ 目录下,每个站点一个 .conf 文件。这样便于管理和备份。


部署前端静态资源

8.1 创建目录结构

# 创建应用目录
mkdir -p /srv/www/frontend-webapp/dist

# 设置目录属主为 nginx 用户
chown -R nginx:nginx /srv/www/frontend-webapp

# 设置目录权限
chmod -R 755 /srv/www/frontend-webapp

8.2 上传前端文件

从开发机将前端构建产物上传到服务器:

# 将前端项目的文件上传到服务器
# 假设前端代码在 /Users/zhoubo/TRAEWORK/Test1/source_code/frontend-webapp/
scp -r /Users/zhoubo/TRAEWORK/Test1/source_code/frontend-webapp/*.html \
       /Users/zhoubo/TRAEWORK/Test1/source_code/frontend-webapp/css \
       /Users/zhoubo/TRAEWORK/Test1/source_code/frontend-webapp/js \
       /Users/zhoubo/TRAEWORK/Test1/source_code/frontend-webapp/users \
       youruser@192.168.1.100:/tmp/frontend-upload/

# 然后在服务器上移动到正式目录
# ssh youruser@192.168.1.100
sudo cp -r /tmp/frontend-upload/* /srv/www/frontend-webapp/dist/
sudo chown -R nginx:nginx /srv/www/frontend-webapp/dist

8.3 验证目录结构

ls -la /srv/www/frontend-webapp/dist/
# 预期看到: index.html, css/, js/, users/ 等

tree /srv/www/frontend-webapp/dist/ -L 2
# 预期结构:
# /srv/www/frontend-webapp/dist/
# ├── index.html
# ├── css/
# │   ├── base.css
# │   ├── components.css
# │   ├── layout.css
# │   ├── login.css
# │   └── pages.css
# ├── js/
# │   ├── common.js
# │   ├── index.js
# │   ├── user-list.js
# │   ├── role-management.js
# │   └── message-list.js
# └── users/
#     └── message-list.html

⚠️ 注意目录权限:前端静态文件要让 nginx 用户有读取权限,否则会出现 403 Forbidden。目录建议 755,文件 644,属主设为 nginx:nginx


配置反向代理(Spring Boot)

9.1 创建站点配置文件

# 创建应用配置文件
vi /etc/nginx/conf.d/webapp.conf

9.2 基础配置内容

# ========================================
# Web 应用服务器配置
# 前端静态资源 + Spring Boot 反向代理
# ========================================

server {
    listen 80;
    server_name _;

    # 前端静态资源根目录
    root /srv/www/frontend-webapp/dist;
    index login.html index.html;

    # ---- 首页与路由回退(默认返回 login.html) ----
    location / {
        try_files $uri $uri/ /login.html;
    }

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

    # ---- 反向代理:API 请求转发到 Spring Boot ----
    location /api/ {
        proxy_pass http://localhost:8081/api/;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # ---- 用户信息页面(子目录) ----
    location /users/ {
        try_files $uri $uri/ /users/message-list.html;
    }
}

proxy_pass 末尾斜杠的含义proxy_pass http://localhost:8081/api/; 末尾带 /,表示将 /api/ 前缀保留转发给后端。即浏览器请求 /api/users → 后端收到 /api/users。如果后端的 Controller 已经映射了 /api/users,使用这种写法。

9.3 测试并重载配置

# 测试配置语法(修改后必做)
nginx -t
# 预期: configuration file /etc/nginx/nginx.conf test is successful

# 平滑重载配置(不中断现有连接)
systemctl reload nginx

完整站点配置示例

以下是包含所有优化项的完整配置,可直接使用:

# ========================================
# Web 应用服务器 — 完整配置
# openSUSE Leap 16.0 + NGINX + Spring Boot
# ========================================

server {
    listen 80;
    server_name _;

    # 字符集
    charset utf-8;

    # 前端静态资源根目录
    root /srv/www/frontend-webapp/dist;
    index login.html index.html;

    # 上传文件大小限制(默认 1m,按需调整)
    client_max_body_size 20m;

    # ---- 首页与路由回退(默认返回 login.html) ----
    location / {
        try_files $uri $uri/ /login.html;
    }

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

    # ---- Bootstrap CDN 本地化(可选) ----
    # 如果将 Bootstrap CSS/JS 下载到本地
    location /bootstrap/ {
        alias /srv/www/frontend-webapp/dist/vendor/bootstrap/;
        expires 30d;
    }

    # ---- 反向代理:API 请求转发到 Spring Boot ----
    location /api/ {
        proxy_pass http://localhost:8081/api/;

        # 传递客户端真实信息
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 代理超时设置
        proxy_connect_timeout 60s;
        proxy_send_timeout    60s;
        proxy_read_timeout    60s;

        # WebSocket 支持(如后续需要)
        # proxy_http_version 1.1;
        # proxy_set_header Upgrade $http_upgrade;
        # proxy_set_header Connection "upgrade";
    }

    # ---- 用户信息子页面 ----
    location /users/ {
        try_files $uri $uri/ /users/message-list.html;
    }

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

    # ---- 自定义错误页面 ----
    error_page 404 /index.html;
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}

可直接使用:将上述完整配置保存为 /etc/nginx/conf.d/webapp.conf,执行 nginx -tsystemctl reload nginx 即可生效,覆盖了静态托管、API 反代、缓存、安全加固与错误页等常见需求。


安装 JDK 运行环境

Spring Boot 4.0 最低要求 JDK 17,在 openSUSE Leap 16.0 上安装 OpenJDK 17:

# 查询可用的 OpenJDK 版本
sudo zypper search -t package "java.*openjdk.*devel"

# 安装 OpenJDK 17 开发包(含 javac)
sudo zypper install java-17-openjdk-devel

# 验证安装
java -version
# 预期: openjdk version "17.0.x"

javac -version
# 预期: javac 17.0.x

⚠️ 注意包名区别java-17-openjdk 是仅运行时(无 javac),java-17-openjdk-devel 是完整开发包。服务器部署如果只需要运行 JAR,装运行时即可;但如果需要在服务器上编译,必须装 -devel 包。

11.1 配置 JAVA_HOME

# 确认 JDK 实际安装路径
readlink -f $(which java)
# 结果类似: /usr/lib64/jvm/java-17-openjdk-17.0.x.x/bin/java

# 创建系统级环境变量配置
cat > /etc/profile.d/java.sh << 'EOF'
export JAVA_HOME=/usr/lib64/jvm/java-17-openjdk
export PATH=$JAVA_HOME/bin:$PATH
EOF

# 赋予执行权限并立即生效
chmod +x /etc/profile.d/java.sh
source /etc/profile.d/java.sh

# 验证
echo $JAVA_HOME
# 预期: /usr/lib64/jvm/java-17-openjdk

为什么用符号链接路径? 使用 /usr/lib64/jvm/java-17-openjdk(不带版本号的符号链接)而非完整路径,这样 JDK 小版本升级后路径不会失效。


打包与部署微服务

12.1 在开发机上打包

在你的 Mac 上,用 Maven 将 user-service 打包为可执行 JAR:

# 进入后端工程目录
cd /Users/zhoubo/TRAEWORK/Test1/source_code/backend-microservices

# 设置 JAVA_HOME(使用开发机的 JDK)
export JAVA_HOME="/Users/zhoubo/Library/Application Support/TRAE SOLO CN/ModularData/ai-agent/vm/tools/opt/openjdk/26.0.1/libexec/openjdk.jdk/Contents/Home"

# 打包(跳过测试,加快速度)
/Users/zhoubo/Downloads/apache-maven-3.6.3/bin/mvn clean package -DskipTests

# 验证生成的 JAR 文件
ls -lh user-service/target/user-service-*.jar
# 预期: user-service/target/user-service-1.0.0-SNAPSHOT.jar

12.2 在服务器上创建部署目录

# 创建应用部署目录
mkdir -p /opt/webapp/user-service
mkdir -p /opt/webapp/user-service/config
mkdir -p /opt/webapp/api-gateway
mkdir -p /opt/webapp/api-gateway/config

# 创建服务运行用户(系统账户,禁止登录)
useradd -r -M -s /usr/sbin/nologin webapp

# 设置目录属主
chown -R webapp:webapp /opt/webapp

12.3 上传 JAR 文件到服务器

# 上传 user-service JAR
scp user-service/target/user-service-1.0.0-SNAPSHOT.jar \
    youruser@192.168.1.100:/tmp/

# 上传 api-gateway JAR
scp api-gateway/target/api-gateway-1.0.0-SNAPSHOT.jar \
    youruser@192.168.1.100:/tmp/

# 上传外部化配置文件
scp user-service/src/main/resources/application.yml \
    youruser@192.168.1.100:/tmp/user-service.yml

12.4 在服务器上放置文件

# 移动 JAR 文件到部署目录
mv /tmp/user-service-1.0.0-SNAPSHOT.jar /opt/webapp/user-service/
mv /tmp/api-gateway-1.0.0-SNAPSHOT.jar /opt/webapp/api-gateway/

# 移动外部化配置文件
mv /tmp/user-service.yml /opt/webapp/user-service/config/application.yml

# 设置文件权限
chown -R webapp:webapp /opt/webapp
chmod 500 /opt/webapp/user-service/user-service-*.jar
chmod 500 /opt/webapp/api-gateway/api-gateway-*.jar
chmod 600 /opt/webapp/user-service/config/application.yml

# 验证目录结构
ls -la /opt/webapp/
# 预期: user-service/  api-gateway/

ls -la /opt/webapp/user-service/
# 预期: user-service-1.0.0-SNAPSHOT.jar  config/

目录结构说明:JAR 文件放在 /opt/webapp/ 下,每个微服务一个子目录。外部化配置文件 application.yml 放在 config/ 子目录中,Spring Boot 启动时会自动加载该目录下的配置,优先级高于 JAR 内嵌的配置。


配置 systemd 服务

将 Spring Boot 微服务注册为 systemd 服务,实现开机自启、异常自动重启、统一日志管理。

13.1 创建 user-service 服务文件

cat > /etc/systemd/system/user-service.service << 'EOF'
[Unit]
Description=User Service (Spring Boot)
After=syslog.target network.target postgresql.service
Wants=postgresql.service

[Service]
User=webapp
Group=webapp
WorkingDirectory=/opt/webapp/user-service

# JDK 路径
Environment="JAVA_HOME=/usr/lib64/jvm/java-17-openjdk"

# JVM 参数
Environment="JAVA_OPTS=-Xms256m -Xmx512m -XX:+UseG1GC"

# Spring Boot 参数
Environment="SPRING_PROFILES_ACTIVE=prod"
Environment="SERVER_PORT=8081"

# 启动命令
ExecStart=${JAVA_HOME}/bin/java $JAVA_OPTS \
    -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} \
    -Dserver.port=${SERVER_PORT} \
    -jar /opt/webapp/user-service/user-service-1.0.0-SNAPSHOT.jar

# 优雅停止
SuccessExitStatus=143
TimeoutStopSec=30

# 异常自动重启
Restart=on-failure
RestartSec=5

# 资源限制
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

13.2 创建 api-gateway 服务文件

cat > /etc/systemd/system/api-gateway.service << 'EOF'
[Unit]
Description=API Gateway (Spring Boot)
After=syslog.target network.target user-service.service
Wants=user-service.service

[Service]
User=webapp
Group=webapp
WorkingDirectory=/opt/webapp/api-gateway

Environment="JAVA_HOME=/usr/lib64/jvm/java-17-openjdk"
Environment="JAVA_OPTS=-Xms128m -Xmx256m -XX:+UseG1GC"
Environment="SERVER_PORT=8080"

ExecStart=${JAVA_HOME}/bin/java $JAVA_OPTS \
    -Dserver.port=${SERVER_PORT} \
    -jar /opt/webapp/api-gateway/api-gateway-1.0.0-SNAPSHOT.jar

SuccessExitStatus=143
TimeoutStopSec=30
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

服务依赖关系user-service 配置了 After=postgresql.serviceWants=postgresql.service,确保数据库就绪后再启动。api-gateway 配置了 After=user-service.service,确保后端服务就绪后再启动网关。注意:测试环境下如果 NGINX 直接代理到 user-service 的 8081 端口,api-gateway 可以暂不启动。

13.3 启动与管理服务

# 重新加载 systemd 配置(新增或修改 service 文件后必做)
systemctl daemon-reload

# 启动 user-service
systemctl start user-service

# 设置开机自启
systemctl enable user-service

# 查看运行状态
systemctl status user-service
# 预期: active (running)

# 同样操作 api-gateway(如需要)
systemctl start api-gateway
systemctl enable api-gateway
systemctl status api-gateway
# 重启服务
systemctl restart user-service

# 停止服务
systemctl stop user-service

# 查看是否已设为开机自启
systemctl is-enabled user-service

# 修改 service 文件后重载并重启
systemctl daemon-reload
systemctl restart user-service

systemd 服务优势:通过 systemd 管理,微服务具备开机自启(服务器重启后自动恢复)、异常自愈(进程崩溃后 5 秒自动重启)、优雅停止(SIGTERM 触发 Spring Boot 优雅关闭)、统一日志(journald 自动收集 stdout/stderr)。


连接 PostgreSQL 数据库

14.1 修改外部化配置文件

编辑服务器上 /opt/webapp/user-service/config/application.yml,将数据源切换为本机 PostgreSQL:

server:
  port: 8081

spring:
  application:
    name: user-service

  # ---- 数据源:PostgreSQL(本机) ----
  datasource:
    url: jdbc:postgresql://localhost:5432/web_app
    username: webapp
    password: YourStrongPass2026
    driver-class-name: org.postgresql.Driver
    hikari:
      minimum-idle: 5
      maximum-pool-size: 20
      idle-timeout: 30000
      max-lifetime: 1800000
      connection-timeout: 30000
      pool-name: UserServiceHikariCP

mybatis:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.example.userservice.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

logging:
  level:
    com.example.userservice: debug
    com.example.userservice.mapper: debug

外部化配置优先级:Spring Boot 加载顺序:config/application.yml(外部)> JAR 内嵌的 application.yml。因此服务器上的外部配置会覆盖打包时的配置,无需重新打 JAR 即可修改数据库连接信息。

14.2 确保数据库已就绪

确认 PostgreSQL 服务已启动、数据库和用户已创建(参考 PostgreSQL 搭建指南):

# 确认 PostgreSQL 运行中
systemctl status postgresql

# 验证 webapp 用户能连接 web_app 数据库
su - postgres -c "psql -d web_app -c '\dt'"
# 预期: 能看到 tb_user 表(如果已建表)

# 确认 5432 端口监听
ss -tlnp | grep 5432

14.3 重启微服务使配置生效

# 重启 user-service 加载新的外部配置
systemctl restart user-service

# 查看启动日志,确认数据库连接成功
journalctl -u user-service -n 50 --no-pager
# 预期日志中看到: HikariPool-1 - Start completed.
# 不应有: Connection refused / Authentication failed

日志管理与部署验证

15.1 日志查看(journald)

Spring Boot 通过 systemd 启动后,stdout/stderr 自动由 journald 收集,无需额外配置日志文件:

# 实时跟踪 user-service 日志
journalctl -u user-service -f

# 查看今天的日志
journalctl -u user-service --since today

# 查看最近 200 行
journalctl -u user-service -n 200 --no-pager

# 查看错误日志
journalctl -u user-service -p err

# 查看指定时间段日志
journalctl -u user-service --since "2026-07-24 10:00" --until "2026-07-24 12:00"

15.2 配置 journald 日志轮转

vi /etc/systemd/journald.conf

# 修改以下配置项:
[Journal]
SystemMaxUse=500M      # 日志最大占用 500MB
SystemKeepFree=1G       # 磁盘至少保留 1GB 空闲
MaxRetentionSec=30day   # 日志保留 30 天

# 重启 journald 使配置生效
systemctl restart systemd-journald

# 手动清理旧日志(如需)
journalctl --vacuum-time=30days
journalctl --vacuum-size=500M

15.3 端到端部署验证

按以下顺序验证整个应用服务器是否正常工作:

# ===== 第 1 步:验证 PostgreSQL =====
systemctl status postgresql
su - postgres -c "psql -c 'SELECT version();'"

# ===== 第 2 步:验证 Spring Boot 后端 =====
systemctl status user-service
curl http://localhost:8081/api/users
# 预期: 返回用户 JSON 列表

# ===== 第 3 步:验证 NGINX 前端 =====
systemctl status nginx
curl -I http://localhost
# 预期: HTTP/1.1 200 OK

# ===== 第 4 步:验证 NGINX 反向代理 =====
curl http://localhost/api/users
# 预期: 返回用户 JSON 列表(经由 NGINX 代理)

# ===== 第 5 步:从开发机浏览器验证 =====
# 浏览器访问: http://192.168.1.100
# 预期: 看到前端页面
# 浏览器访问: http://192.168.1.100/api/users
# 预期: 返回用户 JSON 数据

15.4 验证所有服务状态一览

# 检查所有关键服务状态
echo "=== PostgreSQL ==="
systemctl is-active postgresql
systemctl is-enabled postgresql

echo "=== user-service ==="
systemctl is-active user-service
systemctl is-enabled user-service

echo "=== api-gateway ==="
systemctl is-active api-gateway
systemctl is-enabled api-gateway

echo "=== NGINX ==="
systemctl is-active nginx
systemctl is-enabled nginx

# 检查端口监听情况
ss -tlnp | grep -E ':(80|8081|5432) '
# 预期: 80 (nginx), 8081 (java), 5432 (postgres) 均在监听

部署完成:当以上所有验证通过,应用服务器搭建完成。浏览器访问 http://服务器IP 可看到前端页面,API 请求通过 NGINX 代理到 Spring Boot 后端,后端连接 PostgreSQL 数据库。所有服务均设置为开机自启,服务器重启后自动恢复。

部署目录结构总览——完成部署后,服务器上的关键目录结构:

/opt/webapp/                          # 微服务部署根目录
├── user-service/
│   ├── user-service-1.0.0-SNAPSHOT.jar
│   └── config/
│       └── application.yml           # 外部化配置(数据库连接等)
└── api-gateway/
    ├── api-gateway-1.0.0-SNAPSHOT.jar
    └── config/

/srv/www/frontend-webapp/dist/        # 前端静态资源
├── index.html
├── css/
├── js/
└── users/

/var/lib/pgsql/data/                  # PostgreSQL 数据目录
/etc/systemd/system/
├── user-service.service              # systemd 服务文件
└── api-gateway.service
/etc/nginx/conf.d/
└── webapp.conf                       # NGINX 站点配置

性能优化配置

以下优化项添加到 /etc/nginx/nginx.confhttp 块内:

http {
    # ... 已有配置 ...

    # ---- gzip 压缩 ----
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml application/json
               application/javascript application/xml application/xml+rss
               image/svg+xml;

    # ---- 请求体大小限制 ----
    client_max_body_size 20m;

    # ---- 客户端超时 ----
    client_body_timeout   30s;
    client_header_timeout 30s;
    keepalive_timeout     65s;

    # ---- 发送文件优化 ----
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # ---- 缓冲区设置 ----
    proxy_buffer_size 16k;
    proxy_buffers 4 16k;

    # 加载站点配置
    include /etc/nginx/conf.d/*.conf;
}

优化要点:gzip 可将文本类资源压缩 60%-80%,显著减少传输量。client_max_body_size 默认仅 1MB,文件上传场景必须调大,否则会返回 413 Request Entity Too Large。tcp_nopush 配合 sendfile 可优化大文件传输效率。


SELinux 配置(重要)

⚠️ Leap 16.0 默认启用 SELinux:openSUSE Leap 16.0 默认以 SELinux 作为安全模块(与 Leap 15.x 默认 AppArmor 不同)。NGINX 可能受 SELinux 策略约束,导致配置正确但访问返回 403/502,或 error.log 出现 Permission denied

17.1 检查 SELinux 状态

# 查看 SELinux 状态
sestatus
getenforce
# Enforcing = 已启用并拦截
# Permissive = 已启用但仅记录不拦截
# Disabled = 已禁用

17.2 排查 SELinux 拦截

# 查看 AVC 拒绝日志
ausearch -m AVC,USER_AVC -ts recent

# 或通过 journalctl 查看
journalctl -t setroubleshoot

# 查看 dmesg 中的 SELinux 信息
dmesg | grep -i selinux

17.3 解决方案

方案一:设置文件上下文(推荐)

# 查看当前文件上下文
ls -Z /srv/www/frontend-webapp/dist/

# 设置为 httpd 可读取的内容类型
semanage fcontext -a -t httpd_sys_content_t "/srv/www/frontend-webapp(/.*)?"
restorecon -Rv /srv/www/frontend-webapp

方案二:临时设为 Permissive 模式(仅排错用)

# 临时切到 Permissive(重启后恢复)
setenforce 0

# 测试是否正常
curl -I http://localhost

# 确认问题后切回 Enforcing
setenforce 1

方案三:切换回 AppArmor(可选)

# 安装 AppArmor
sudo zypper install apparmor apparmor-utils

# 切换 LSM(需要重启)
# 参考: https://en.opensuse.org/SDB:AppArmor
# 编辑 /etc/default/grub,在 GRUB_CMDLINE_LINUX 中:
#   将 security=selinux 改为 security=apparmor
# 更新 GRUB:
grub2-mkconfig -o /boot/grub2/grub.cfg
reboot

# 重启后验证
sestatus # 应显示 disabled
aa-status # 应显示 AppArmor 已加载

⚠️ 生产环境建议:测试环境下可以将 SELinux 设为 Permissive 或切换到 AppArmor 以减少配置复杂度。但生产环境建议保持 SELinux Enforcing 并正确配置上下文,以获得最高安全性。


HTTPS 配置(可选)

测试环境可先用 HTTP,后续如需 HTTPS 可使用自签名证书或 Let's Encrypt:

18.1 生成自签名证书

# 创建证书目录
mkdir -p /etc/nginx/ssl

# 生成自签名证书(有效期 365 天)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/ssl/selfsigned.key \
  -out /etc/nginx/ssl/selfsigned.crt \
  -subj "/C=CN/ST=Shanghai/L=Shanghai/O=Test/CN=192.168.1.100"

# 设置权限
chmod 600 /etc/nginx/ssl/selfsigned.key

18.2 配置 HTTPS 站点

# HTTP 跳转 HTTPS(可选)
server {
    listen 80;
    server_name _;
    return 301 https://$host$request_uri;
}

# HTTPS 站点
server {
    listen 443 ssl;
    server_name _;

    ssl_certificate     /etc/nginx/ssl/selfsigned.crt;
    ssl_certificate_key /etc/nginx/ssl/selfsigned.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # 以下配置与 HTTP 版本相同
    root /srv/www/frontend-webapp/dist;
    index login.html index.html;

    location / {
        try_files $uri $uri/ /login.html;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    location /api/ {
        proxy_pass http://localhost:8081/api/;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
nginx -t
systemctl reload nginx

# 验证 HTTPS
curl -k https://localhost
# -k 忽略自签名证书警告

多站点配置

/etc/nginx/conf.d/ 下为每个站点创建独立配置文件:

# /etc/nginx/conf.d/site-a.conf — 站点 A
server {
    listen 80;
    server_name app1.example.com;

    root /srv/www/app1/dist;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}
# /etc/nginx/conf.d/site-b.conf — 站点 B(纯代理)
server {
    listen 80;
    server_name app2.example.com;

    location / {
        proxy_pass http://localhost:8082;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

基于端口的虚拟主机(不同端口区分):

# 站点 A:端口 8080
server {
    listen 8080;
    root /srv/www/app1/dist;
    location / { try_files $uri $uri/ /index.html; }
}

# 站点 B:端口 8085
server {
    listen 8085;
    location / { proxy_pass http://localhost:8082; }
}
# 测试配置
nginx -t

# 重载
systemctl reload nginx

# 如需开放额外端口
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload

站点区分方式:多站点可按域名(server_name)区分,也可按端口区分。域名方式的多个站点共享 80/443,更正式;端口方式适合内网临时服务,但不同端口每次都要在防火墙放行。


常见问题排查

问题 1:访问返回 403 Forbidden

常见原因: 文件权限不足或 SELinux 拦截

# 1. 检查文件权限
ls -la /srv/www/test1/index.html
# 确认 nginx 用户有读取权限

# 2. 检查目录权限
namei -l /srv/www/test1/index.html
# 每一级目录都应有 o+x 权限

# 3. 检查 SELinux
sestatus
# 如果是 Enforcing,查看拒绝日志
ausearch -m AVC -ts recent | grep nginx

# 4. 查看 NGINX 错误日志
tail -20 /var/log/nginx/error.log

问题 2:API 返回 502 Bad Gateway

常见原因: Spring Boot 服务未启动或端口不对

# 1. 检查 Spring Boot 是否运行
systemctl status user-service
# 或者
ps aux | grep java

# 2. 检查 8081 端口是否监听
ss -tlnp | grep 8081

# 3. 本机直接测试后端
curl http://localhost:8081/api/users

# 4. 检查 NGINX 错误日志
tail -20 /var/log/nginx/error.log

问题 3:上传文件返回 413

原因: client_max_body_size 太小

# 在 nginx.conf 的 http 块或站点配置的 server 块中添加
vi /etc/nginx/nginx.conf
# http { ... client_max_body_size 100m; ... }

# 重载
nginx -t && systemctl reload nginx

问题 4:修改配置后不生效

# 1. 检查配置语法
nginx -t

# 2. 确认已重载(不是 restart)
systemctl reload nginx

# 3. 查看完整生效配置
nginx -T | grep -A 20 "server_name"

# 4. 清除浏览器缓存后重新访问
# 或使用 curl 加 -H "Cache-Control: no-cache"
curl -H "Cache-Control: no-cache" http://localhost

问题 5:SELinux 导致 proxy_pass 502

# 查看是否有网络连接拒绝
ausearch -m AVC -ts recent | grep nginx

# 如果确认是 SELinux 拦截了 NGINX 到后端的连接
# 临时设为 Permissive 测试
setenforce 0
curl -I http://localhost/api/users

# 如果正常了,说明确实是 SELinux 问题
# 可以设置 Permissive 模式或配置策略

问题 6:FTP 上传文件返回 553 Could not create file

常见原因: vsftpd 配置限制、目录权限不足、AppArmor/SELinux 拦截

# 1. 检查目录权限,确认 ftp 用户有写权限
ls -la /srv/www/test1

# 2. 检查 vsftpd 关键配置项
grep -E "write_enable|local_umask|allow_writeable_chroot" /etc/vsftpd.conf

# 3. 检查 AppArmor/SELinux 是否拦截
sudo aa-status | grep vsftpd
sudo dmesg | grep -i apparmor | grep vsftpd

# 4. 查看 vsftpd 最近日志
sudo journalctl -u vsftpd --no-pager -n 20
# 1. 设置正确的目录属主
sudo chown -R 用户名:nginx /srv/www/test1

# 2. 确认 vsftpd.conf 中以下配置项
#    write_enable=YES
#    local_umask=022
#    allow_writeable_chroot=YES
sudo vi /etc/vsftpd.conf

# 3. 重启 vsftpd 使配置生效
sudo systemctl restart vsftpd

问题 7:推荐使用 SFTP 替代 FTP 进行文件上传

适用场景: FTP 553 错误在完成上述修复后仍然存在,或希望使用更可靠的文件传输方式

⚠️ 推荐方案:如果 FTP 553 错误持续存在,建议直接改用 SFTP(SSH File Transfer Protocol)。SFTP 复用 SSH 隧道(端口 22),无需额外安装或启动 vsftpd 服务,传输过程全程加密,安全性更高,且不会触发 vsftpd 相关的 AppArmor/SELinux 拦截问题。

SFTP 的优势:使用 SSH 隧道(端口 22),无需额外启动 vsftpd 服务;传输加密更安全;不受 vsftpd 配置及 AppArmor/SELinux 限制;只要服务器开启了 SSH 即可使用。

# FileZilla — SFTP 站点配置
# 在 FileZilla 中配置 SFTP 连接

# 1. 文件 (File) → 站点管理器 (Site Manager) → 新建站点 (New Site)

# 2. 协议 (Protocol): SFTP - SSH File Transfer Protocol

# 3. 主机 (Host): 192.168.1.162

# 4. 登录类型 (Logon Type): Normal

# 5. 用户 (User): 你的用户名
#    密码 (Password): 你的密码

# 6. 默认远程目录 (Default remote directory):
#    前端文件: /srv/www/test1
#    后端文件: /opt/webapp/test1

命令速查表

操作 命令
安装 NGINX zypper install nginx
启动服务 systemctl start nginx
停止服务 systemctl stop nginx
重启服务 systemctl restart nginx
平滑重载 systemctl reload nginx
设置开机自启 systemctl enable nginx
查看状态 systemctl status nginx
测试配置语法 nginx -t
查看完整生效配置 nginx -T
查看 NGINX 版本 nginx -v
查看访问日志 tail -f /var/log/nginx/access.log
查看错误日志 tail -f /var/log/nginx/error.log
查看 systemd 日志 journalctl -u nginx -f
开放 HTTP 端口 firewall-cmd --permanent --add-service=http && firewall-cmd --reload
查看防火墙规则 firewall-cmd --list-all
查看 SELinux 状态 sestatus
临时关闭 SELinux setenforce 0
检查端口监听 ss -tlnp | grep nginx

部署一台应用服务器,技术之外更要看"一层入口、一条命令、一键重启"这件事。把复杂的前后端串起来,NGINX 给了个足够务实的中间答案——静态给你,API 也给你,剩下的交给 systemd 去守护。

评论 (0)

还没有评论,来抢沙发。

发表评论