安全 Linux 服务器设置与应用部署

发布: (2025年12月18日 GMT+8 22:00)
11 min read
原文: Dev.to

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.15432

🔐 加密、私密、安全。

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/staticpublic/)会被自动省略。如果不复制这些文件,应用虽然可以运行,但资源会返回 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 运行。它们只构建一次,然后直接由反向代理提供服务。

  1. 将 env 文件放在 项目根目录:

    .env.production
  2. 显式构建

    npm run build

    构建产出(HTMLJSCSS、资源)现在是完全静态的,并已注入相应的值。

  3. 给 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 进程。
  • 仅公开 80443 端口;应用程序从不直接绑定到互联网。
  • 静态站点 零运行时风险
  • 动态应用程序通过 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 完成它已经最擅长的工作。

如果你端到端地遵循本指南,你的服务器已经会比大多数生产环境更安全。

部署愉快(且安全)!🚀

Back to Blog

相关文章

阅读更多 »

ASR(自动语音识别)

ASR 自动语音识别的封面图片 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-...