一台闲置的旧电脑,装上 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设为no或prohibit-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 -t再systemctl 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.service和Wants=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.conf 的 http 块内:
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)
还没有评论,来抢沙发。