安全 Linux 服务器设置与应用部署
Source: Dev.to
部署一个应用程序很容易。
要安全地运行它,使得一个被攻破的应用不会导致整个服务器瘫痪,需要纪律性和结构化的做法。
本指南记录了我们遵循的完整流程,以便:
- 为全新 Linux 服务器做准备
- 部署数据库和应用程序
- 保持系统安全、隔离且易于维护
该方法已经经受实战考验,适用于真实的生产服务器。
此设置适用于
| 类别 | 示例 |
|---|---|
| Node.js 后端 | NestJS, Express, Fastify |
| Next.js | 独立构建 |
| 静态前端 | React, Vite |
| 数据库(Docker) | PostgreSQL, MongoDB, Redis |
| 反向代理 | Caddy, Nginx (HTTPS) |
Source:
核心原则
- Root 不是应用运行时 – 永远不要以 root 身份运行服务。
- 一个应用 = 一个服务用户 – 每个应用都有自己权限受限的 Linux 用户。
- 人类部署,服务运行 – 只有人类拥有 sudo 权限。
- 数据库默认私有 – 仅绑定到
127.0.0.1。 - 反向代理是唯一的公共入口点。
- 假设某个应用最终会被攻破 – 设计时要考虑隔离。
我们的目标很简单:如果一个应用被入侵,其他所有应用必须保持安全。
1. 创建普通管理员用户
在全新服务器上,通常以 root 身份开始。
# Create a non‑root admin user (example: dev)
adduser dev
usermod -aG sudo dev
为什么?
- Root SSH 访问危险。
- 使用
sudo可审计。 - 事故更容易恢复。
2. 设置 SSH 密钥认证
# Generate a modern SSH key (ed25519)
ssh-keygen -t ed25519
复制公钥到服务器:
mkdir -p /home/dev/.ssh
nano /home/dev/.ssh/authorized_keys # paste your public key here
修复权限:
chown -R dev:dev /home/dev/.ssh
chmod 700 /home/dev/.ssh
chmod 600 /home/dev/.ssh/authorized_keys
加固 sshd_config
sudo nano /etc/ssh/sshd_config
确保以下行存在(或添加它们):
PermitRootLogin no
PubkeyAuthentication yes
# Disable password login
Match all
PasswordAuthentication no
安全地重新加载 SSH:
sudo systemctl reload ssh
3. 配置防火墙 (UFW)
# Allow only what’s required
sudo ufw allow OpenSSH # port 22
sudo ufw allow 80 # HTTP
sudo ufw allow 443 # HTTPS
sudo ufw enable
sudo ufw status verbose
结果: 只有端口 22、80、443 对外可访问。
4. 安装 Docker(官方仓库)
绝不要 使用默认 Ubuntu 仓库中的
docker.io包。
# Follow Docker’s official installation guide for your distro.
# After installation, add the admin user to the docker group:
sudo usermod -aG docker dev
重新登录并验证:
docker ps
重要规则
- 绝不要 将服务用户添加到
docker组。 - Docker 以相当于 root 的权限运行,因此只能允许管理员用户(
dev)使用。
5. 安全部署数据库(Docker)
示例:PostgreSQL(安全)
docker run -d \
--name postgres \
--restart unless-stopped \
-e POSTGRES_USER=appuser \
-e POSTGRES_PASSWORD=STRONG_PASSWORD \
-e POSTGRES_DB=appdb \
-v pgdata:/var/lib/postgresql \
-p 127.0.0.1:5432:5432 \
postgres:18
验证绑定:
ss -tulpn | grep 5432
# 预期输出: 127.0.0.1:5432
为什么绑定到 127.0.0.1?
Docker 不会遵守 UFW 对已发布端口的规则。它会插入自己的 iptables 规则,因此即使 UFW 阻止该端口,绑定到 0.0.0.0 的容器仍会公开可访问。绑定到 127.0.0.1 能保证数据库只能 从宿主机本身 访问。
从本地机器访问(SSH 隧道)
ssh -N -L 5432:127.0.0.1:5432 dev@SERVER_IP
现在在本地连接:
| 主机 | 端口 |
|---|---|
| 127.0.0.1 | 5432 |
🔐 加密、私密、安全。
6. 管理私有仓库访问(Deploy Keys)
对于每个私有仓库,以管理员用户身份生成专用的 SSH 部署密钥:
ssh-keygen -t ed25519 -C "deploy-myapp" -f ~/.ssh/id_ed25519_myapp
- 将 公钥 (
id_ed25519_myapp.pub) 添加为 GitHub 中的 Deploy key(只读)。 - 使用 SSH 别名进行克隆(绝不要使用 HTTPS)。
7. 为每个应用创建受限的服务用户
sudo adduser \
--system \
--no-create-home \
--group \
--shell /usr/sbin/nologin \
svc-myapp
此用户的特性
- 不能通过 SSH 登录。
- 没有 shell。
- 没有 sudo 权限。
- 仅拥有其应用目录的所有权。
部署应用
# 切换到管理员用户 (dev) 并克隆仓库
cd /var/apps
git clone git@github.com-myapp:org/repo.git
cd repo
# 安装依赖并构建
npm ci
npm run build
npm prune --production
Humans build. 在生产环境中,环境变量 永不 提交到 Git。
8. 安全存储运行时环境变量
创建一个系统管理的 env 文件(归 root 所有):
# /etc/systemd/system/myapp.env
# Example content:
# DATABASE_URL=postgres://appuser:STRONG_PASSWORD@127.0.0.1:5432/appdb
# OTHER_SECRET=...
- 该文件在运行时由
systemd加载。 - 如果 Next.js 项目使用以
NEXT_PUBLIC_为前缀的变量,则必须在 构建时 提供这些变量,因为编译器会将它们嵌入代码。
使用公共变量进行构建(如有需要)
sudo -E bash -c '
set -a
source /etc/systemd/system/myapp.env
set +a
npm run build
'
如果 没有 NEXT_PUBLIC_* 变量,则此步骤不是必需的——通过 systemd 的运行时注入即可满足需求。
9. 准备独立的 Next.js 构建
在将 Next.js 构建为独立应用时,需要手动复制静态资源:
# After `npm run build` (or `next build`)
mkdir -p .next/standalone/.next
cp -r .next/static .next/standalone/.next/
cp -r public .next/standalone/
为什么?
独立输出只包含服务器代码;静态文件(_next/static、public/)会被自动省略。如果不复制这些文件,应用虽然可以运行,但资源会返回 404。
10. 在应用程序目录上设置权限
sudo chown -R svc-myapp:svc-myapp /var/apps/myapp
sudo chmod -R o-rwx /var/apps/myapp
# Even the admin user (dev) now gets permission denied – intentional.
11. 创建 Systemd 服务
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target
[Service]
User=svc-myapp
Group=svc-myapp
WorkingDirectory=/var/apps/myapp
EnvironmentFile=/etc/systemd/system/myapp.env
ExecStart=/usr/bin/node dist/main.js
Restart=always
RestartSec=3
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/apps/myapp
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictNamespaces=true
LockPersonality=true
RestrictSUIDSGID=true
CapabilityBoundingSet=
AmbientCapabilities=
启用并启动服务:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
sudo systemctl status myapp.service
12. Summary Checklist
| 步骤 | 完成? |
|---|---|
创建管理员用户 (dev) | ✅ |
设置 SSH 密钥认证并加固 sshd | ✅ |
| 配置 UFW(22、80、443) | ✅ |
| 从官方仓库安装 Docker | ✅ |
仅将管理员用户添加到 docker 组 | ✅ |
部署绑定到 127.0.0.1 的数据库 | ✅ |
| 为每个仓库生成部署密钥 | ✅ |
创建低权限服务用户 (svc‑myapp) | ✅ |
| 克隆、构建并清理应用 | ✅ |
将环境变量存放在 /etc/systemd/system/*.env 中 | ✅ |
| 构建 Next.js 独立版(复制静态资源) | ✅ |
| 对应用文件设置严格权限 | ✅ |
| 创建加固的 systemd 服务 | ✅ |
| 验证防火墙、Docker 和服务状态 | ✅ |
在每台新服务器上遵循本指南,您将拥有一个 安全、隔离且易于维护 的生产环境,单个被攻破的应用也无法导致整台主机宕机。部署愉快!
Mask=0077
[Install]
WantedBy=multi-user.target
启用并启动(替代)
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
静态前端应用(React、Vite 等)
静态应用 不通过 systemd 运行。它们只构建一次,然后直接由反向代理提供服务。
-
将 env 文件放在 项目根目录:
.env.production -
显式构建:
npm run build构建产出(
HTML、JS、CSS、资源)现在是完全静态的,并已注入相应的值。 -
给 Caddy 只读权限,指向静态构建文件夹:
sudo chown -R svc-frontend:svc-frontend /var/apps/frontend sudo chmod -R 755 /var/apps/frontend/dist
为什么这样做
- Caddy 只需要读取静态文件;不需要写权限。
- 防止意外或恶意的文件修改。
- 静态应用 没有运行时、没有开放端口、没有后台进程 → 大幅降低攻击面。
Caddy 是服务器的 唯一公共入口点。应用在内部通过私有端口运行(例如 127.0.0.1:5000)。
示例反向代理配置
api.example.com {
reverse_proxy 127.0.0.1:5000
}
备注
- 应用端口 未公开暴露。
- 防火墙阻止直接访问;只有 Caddy 能访问它。
- 适用于 NestJS、Next.js(独立模式)、Express 等。
Caddyfile for Static Site
app.example.com {
root * /var/apps/frontend/dist
encode gzip zstd
try_files {path} {path}/ /index.html
file_server
}
这段配置的作用
- 直接提供静态文件。
- 支持客户端路由(SPA)。
- 启用压缩。
- 不需要 Node.js 进程。
- 仅公开 80 和 443 端口;应用程序从不直接绑定到互联网。
- 静态站点 零运行时风险。
- 动态应用程序通过 systemd 和防火墙进行隔离。
这种清晰的分离使服务器 安全、可观测且易于理解。
部署动态应用
sudo chown -R dev:dev /var/apps/myapp
cd /var/apps/myapp
git pull
npm ci
npm run build
npm prune --production
sudo chown -R svc-myapp:svc-myapp /var/apps/myapp
sudo chmod -R o-rwx /var/apps/myapp
sudo systemctl restart myapp
⚠️ 永不 sudo git pull
- 防止特权提升。
- 阻止应用之间的横向移动。
- 避免意外的数据泄露。
- 降低暴露数据库的风险。
- 防止因应用漏洞导致的 root 级别妥协。
即使一个应用被入侵,系统仍能存活。其他应用仍能存活。数据仍能存活。
安全哲学
- 安全不是关于工具;而是关于明确的边界和乏味的默认设置。
- 此设置避免复杂性,避免神奇,并依赖 Linux 完成它已经最擅长的工作。
如果你端到端地遵循本指南,你的服务器已经会比大多数生产环境更安全。
部署愉快(且安全)!🚀