如何安全地将 Node 应用程序部署到 Ubuntu 服务器

本博客介绍如何使用 Nginx 将 Node 应用部署到服务器,无论是“VPS”、“VDS”还是“专用服务器”。本文假设您熟悉基本的“Linux”和“git”命令。这适用于运行服务器的任何 Node 应用,无论是“Express”应用、“Next.js”应用、“Remix”应用等。另外需要注意的是,我们将部署应用程序代码;数据库将被分离。

假设

  • 具有完全根访问权限和域名的托管服务
  • 操作系统:Ubuntu 22.04
  • IPv4:172.172.172.172
  • IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334
  • 域名:example.com
  • 在本地计算机上使用 bash 终端
  • 代码托管在 GitHub 上
  • 工作流程步骤

    1. 保护您的服务器

    A. 登录到您的服务器

  • 打开你的终端(bash、zsh 等)
  • ssh root@172.172.172.172

    系统将提示您提供 root 密码。输入密码后,您将登录到服务器。您应该看到类似以下内容:

    root@vm172048:~$

    如果是的话,您已经成功以 root 用户身份登录。

    B. 服务器强化

    现在我们已经进入了机器内部,我们可以开始安装必要的软件包和软件,但在此之前,让我们先升级系统。在终端中输入以下命令:

    apt update && apt upgrade -y

    现在,所有当前软件包都已更新至最新补丁版本,从而保证系统免受“未修补的漏洞利用”的侵害。

    C.创建非Root用户

    不建议以 root 用户身份部署,因为 root 用户拥有所有服务器资源的完全访问权限。因此,让我们创建一个名为“admin”的**非 root 用户**,并将其添加到“sudo”组以使用需要 root 权限的命令。

    要创建 `sudo` 用户:

    useradd -m -s /bin/bash admin

    这将创建一个名为“admin”的新用户,您可以使用“groups”命令检查该用户的组。

    groups admin

    我们将 `admin` 用户添加到 `sudo` 组:

    usermod -aG sudo admin

    这会将用户添加到“sudo”组,而不会将其从原始“admin”组中删除。

    现在让我们为用户**创建密码**:

    sudo passwd admin

    系统将提示您输入新密码并重新输入。

    要**检查密码是否设置正确**,请打开一个新终端并输入以下命令进行检查:

    ssh admin@172.172.172.172

    系统将提示您输入新密码。输入后,您应该会看到类似以下内容的内容:

    admin@vm172048:~$

    D.使用 SSH 连接到服务器

    不建议使用**密码登录**。您需要使用 SSH(安全外壳)并确保**SSH 是唯一的登录方式**。

    如果您是 `git` 用户,则很可能您已经设置了 `ssh key`。如果您还没有 `ssh key`,请使用以下命令在您的**本地机器**上生成新的 SSH 密钥:

    ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/example

    激活 SSH 代理:

    eval "$(ssh-agent -s)" && ssh-add ~/.ssh/example

    使用 `cat ~/.ssh/example.pub` 复制密钥。登录服务器然后将其粘贴到服务器的 `~/.ssh/authorized_keys` 中:

    mkdir ~/.ssh
    touch ~/.ssh/authorized_keys
    sudo nano ~/.ssh/authorized_keys

    完成所有这些后,您应该能够无需使用密码登录。

    E. 在服务器上禁用 Root 和密码登录

    要关闭用户名和密码登录,请输入:

    sudo nano /etc/ssh/sshd_config

    找到这些值并按如下方式设置它们:

    Port 1234 # Change the default port (use a number between 1024 and 65535)
    PermitRootLogin no # Disable root login
    PasswordAuthentication no # Disable password authentication
    PubkeyAuthentication yes # Enable public key authentication
    AuthorizedKeysFile .ssh/authorized_keys # Specify authorized_keys file location
    AllowUsers admin # Only allow specific users to log in

    这将禁止您复制公钥的用户使用除 SSH 之外的所有登录方法。它会停止以 root 身份登录,只允许您指定的用户登录。按“CTRL+S”保存,按“CTRL+X”退出文件编辑器。重新启动 SSH:

    sudo service ssh restart

    现在尝试以 root 身份登录,看看是否不允许。由于您将默认 SSH 端口从 22 更改为 1234,因此您需要在登录时提及端口。

    ssh -p 1234 root@172.172.172.172

    由于 root 登录已被禁用,因此这将禁止您登录。要登录,请使用:

    ssh -p 1234 admin@172.172.172.172

    此外,不言而喻,**您需要妥善保管私钥**,如果丢失了私钥,**您将无法再远程进入**。

    F. 防火墙配置

    Ubuntu 默认自带 `ufw` 防火墙,如果没有,可以使用以下命令安装:

    sudo apt install ufw -y

    要查看“ufw”的当前状态,请输入:

    sudo ufw status

    这将显示防火墙的当前状态。要启用防火墙,请运行以下命令:

    sudo ufw enable

    首先,运行一些默认策略:

    sudo ufw default deny incoming && sudo ufw allow outgoing

    现在,由于我们将 SSH 端口更改为“1234”,因此请允许它通过防火墙。除此之外,我们将使用端口 80 和 443 通过 HTTP 和 HTTPS 进行 Web 服务,因此也请允许它们。

    sudo ufw allow 1234,80,443

    为了进一步提高通过 SSH 进行暴力登录的能力,请使用以下命令:

    sudo ufw limit 1234

    这会将端口 1234 限制为 30 秒内来自单个 IP 的 6 个连接。如果您错误地打开了任何端口,则可以使用以下命令拒绝连接:

    sudo ufw deny 

    现在,要查看当前状态,请使用:

    sudo ufw status verbose

    重新启动“ufw”以确保所有规则都已应用:

    sudo ufw reload

    启用防火墙后,**切勿**在未启用 `ssh` 连接规则的情况下**退出远程服务器连接**。否则,**您将无法登录自己的服务器**。

    G.Fail2Ban 配置

    Fail2Ban 为 Ubuntu 22.04 提供了保护盾,专门用于阻止对 SSH 和 FTP 等基本服务的未经授权的访问和暴力攻击。要安装“fail2ban”,请使用以下命令:

    sudo apt install fail2ban

    安装完成后,使用以下命令启动 Fail2ban 服务:

    sudo systemctl start fail2ban

    要在 Ubuntu 22.04 上启用 Fail2ban 以便它在系统启动时自动启动,请使用:

    sudo systemctl enable fail2ban

    接下来,我们需要使用以下命令验证 Fail2ban 是否已启动并正常运行:

    sudo systemctl status fail2ban

    现在让我们配置Fail2ban。主要配置位于`/etc/fail2ban/jail.conf`,但建议创建一个本地配置文件:

    sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

    现在打开“jail.local”文件并调整默认部分中的值:

    bantime = 10m
    findtime = 10m
    maxretry = 5

    Fail2Ban 的工作原理是,在定义的 **查找时间** 内检测到重复失败后,在指定的 **禁止时间** 内禁止某个 IP。**最大重试** 设置决定了禁止 IP 之前允许的失败次数。

    现在重新启动 Fail2ban 服务:

    sudo systemctl restart fail2ban

    要查看哪个 jail 激活了哪个服务,请输入:

    sudo fail2ban-client status

    如果您已经走到这一步,恭喜您!您已保护好服务器。现在可以部署您的“webApp”了。输入“exit”结束会话。

    exit

    2.DNS 配置

    我们的网站将以域名为人所知,在本例中为“example.com”。为了使域名指向我们的服务器,我们需要在域名提供商端进行一些 DNS 配置。

    a. 登录您的域名提供商的网站。

    b. 导航到“example.com”,然后管理 DNS 管理。

    c.现在更新 IPv4 和 IPv6 地址的“A”和“AAAA”记录。

    d. 接下来,更新 `CNAME` 记录以将 `www.example.com` 转发到 `example.com`。

    `CNAME` 将子域名映射到另一个域名。

    现在可能需要几分钟才能传播到所有 DNS 服务器。要检查“example.com”是否解析为您的主机“IP 地址”,请使用此在线工具检查 DNS 传播:DNS 检查器。

    3.部署 Web 应用

    要部署,我们需要安装几个软件包。首先,我们将使用“git”从“GitHub”克隆存储库。由于它是一个 Node 应用程序,我们需要在系统上安装“Node”;我将使用 Node 版本 20。我们将使用“Node”附带的“npm”。最后,为了将应用程序作为后台进程进行管理,我们将使用“pm2”。

    首先,使用以下命令登录您的服务器:

    ssh -p 1234 admin@172.172.172.172

    A.安装所有必要的依赖项

    a.首先检查安装:

    nginx -v
    node -v
    npm -v
    git --version
    pm2 --version

    b.然后更新到最新版本并删除不必要的包:

    sudo apt update && sudo apt upgrade -y && sudo apt autoremove -y

    c. 安装最新版本的 `git`:

    sudo apt install git -y

    d. 安装 `Node` 版本 20 及其附带的 `npm`(Node 包管理器):

    curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
    sudo apt-get install -y nodejs

    e. 安装 `nginx` Web 服务器:

    sudo apt install -y nginx

    B.从 GitHub 克隆代码库

    我们的代码库将托管在“GitHub”上。我们将使用部署密钥将库克隆到我们的服务器。

    a. 首先,生成一个名为“example”的部署密钥:

    ssh-keygen -t ed25519 -C "your_email@gmail.com" -f ~/.ssh/example

    系统将提示您输入密码;只需按回车键即可。这将在您的 `~/.ssh` 文件夹中生成一对名为 `example.pub` 和 `example` 的公钥-私钥对。

    b. 确保 `~/.ssh` 文件夹归 `admin` 所有:

    sudo chown -R admin ~/.ssh

    c. 将 GitHub 的 SSH 服务器公钥添加到服务器的“known_hosts”文件中:

    ssh-keyscan -t ed25519 github.com >> ~/.ssh/known_hosts

    d.接下来,将密钥输出到终端后,复制 SSH 公钥:

    cat ~/.ssh/example.pub

    e. 使用复制的密钥作为 GitHub 中的部署密钥:

  • 前往你的 GitHub Repo
  • 点击“设置”选项卡
  • 单击侧栏中的“部署密钥”选项
  • 单击“添加部署密钥”按钮,然后粘贴复制的 SSH 公钥,并使用您选择的名称
  • 点击添加密钥
  • f. 使用以下命令将项目从 GitHub Repo 克隆到服务器主目录中:

    git clone git@github.com:admin7374/example_app.git

    这里,“admin7374”是 GitHub 用户名,“example_app”是我们即将部署的 Node 应用。这将在服务器上克隆代码库。

    C.使用 pm2 运行应用程序

    现在是时候在后台构建并运行 Node 应用程序了:

    a. 导航到项目文件夹:

    cd ~/example_app

    b.创建一个 `.env` 文件:

    touch .env

    c. 打开 `.env` 文件并粘贴您的环境变量:

    sudo nano .env

    `.env` 文件示例:

    PORT = 8001
    DATABASE_URL = "database_url"

    粘贴后,单击“CTRL + S”和“CTRL + X”保存并退出。

    d. 在您的 repo 代码中创建一个 `ecosystem.config.cjs` 文件(最好在 GitHub repo 中创建):

    touch ecosystem.config.cjs
    sudo nano ecosystem.config.cjs

    然后粘贴下面的代码:

    module.exports = {
      apps : [
          {
            name: "example_app",
            script: "npm start",
            port: 8001 // optional, if have port set in app
          }
      ]
    }

    上述代码将在端口“8001”上运行 Node 应用;请确保它与应用程序中定义的端口匹配。“script”通常是 Node 应用的运行方式。它假定您的“package.json”中有一个“npm start”脚本来运行构建代码。

    e. 接下来,使用以下命令安装必要的 Node 模块:

    npm ci

    上述命令创建一个“node_modules”文件夹,其中包含运行代码所需的所有包。

    f.现在让我们构建代码。输入:

    npm run build

    上述脚本将使用“package.json”中定义的“build”脚本构建用于分发的代码。

    g. 启动时添加 PM2 进程:

    sudo pm2 startup

    h. 使用 `pm2` 启动 Node App:

    pm2 start ecosystem.config.cjs

    i. 保存PM2进程:

    pm2 save

    这将保存在后台继续运行的进程。

    j.列出所有在后台运行的 PM2 进程:

    pm2 list

    k. 如果需要重新加载以进行重新部署,请使用:

    pm2 reload example_app

    l. 要检查 PM2 进程日志,请使用:

    pm2 monit

    这将打开一个交互式终端,显示每个进程的日志和元数据。输入“q”退出。

    m. 使用以下方法检查应用程序是否正常运行:

    curl localhost:8001

    如果应用程序正常运行,这应该可以正确输出。

    D. 使用 NGINX 提供应用程序

    现在是时候使用 Nginx 来最终为应用程序提供服务了。Nginx 是一个强大的 Web 服务器、反向代理和负载均衡器。在本例中,我们将使用 `nginx` 反向代理功能将运行在 `localhost:8001` 上的应用程序提供给互联网。

    a. 启动并启用 `nginx`:

    sudo systemctl start nginx
    sudo systemctl enable nginx

    b. 验证“nginx”是否已启动并正在运行:

    sudo systemctl status nginx

    如果一切顺利,输出应该表明 Nginx 服务处于“活动(正在运行)”状态。

    c.如果您希望通过 Web 浏览器确认 Nginx 的操作,请导航至:

    http://example.com

    这应该显示默认的“nginx”页面。

    d. 如果没有显示,则 `ufw` 防火墙可能阻止了端口 `80` 和 `443`。要允许它们通过 `ufw` 防火墙,请使用:

    sudo ufw allow 'Nginx Full'

    e. 与许多服务器软件一样,Nginx 依赖配置文件来决定其行为。首先为您的网站创建一个配置文件:

    sudo nano /etc/nginx/sites-available/example.com

    f. 在此文件中,输入以下代理传递配置:

    server {
        listen 80;
        listen [::]:80;
        server_name example.com www.example.com;
    
        location / {
            proxy_pass http://localhost:8001;
    
            # Proxy Params - pass client request information to the proxied server
            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;
    
            # If you need to upload files larger than 1M, use the below directive
            # client_max_body_size 500M;
    
            # Web socket upgrade configuration
            # Uncomment the following lines if you're using websockets in your app
            # proxy_http_version 1.1;
            # proxy_set_header Upgrade $http_upgrade;
            # proxy_set_header Connection "upgrade";
        }
    
        # Logging
        access_log /var/log/nginx/example.com.access.log;
        error_log /var/log/nginx/example.com.error.log warn;
    }

    代理传递配置直接提供文件服务;它将请求代理到本地应用程序(在本例中,在端口 8001 上运行)。

    g. 配置文件创建后,尚未生效。要激活它,您需要创建指向“sites-enabled”目录的符号链接:

    sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com

    将此步骤视为“发布”您的配置,使其生效并准备好处理流量。

    h. 上线前测试配置:

    sudo nginx -t

    然后 Nginx 会解析你的配置并返回反馈。成功表示你的配置没有错误。

    i. 上线时间到了。这需要重新加载:

    sudo systemctl reload nginx

    j.现在检查 Web 浏览器,转到:

    http://example.com

    k. 我们当前的网站配置通过端口 80 上的 HTTP 提供内容,这些内容未加密。让我们通过 Let's Encrypt 对其进行加密。首先,安装 certbot:

    sudo apt update
    sudo apt install software-properties-common
    sudo add-apt-repository ppa:certbot/certbot
    sudo apt update
    sudo apt install certbot python3-certbot-nginx

    l.使用 certbot 生成 SSL 证书:

    sudo certbot --nginx -d example.com -d www.example.com

    只需按照提示中的说明进行操作即可。您的网站将使用适当的 SSL/TLS 加密证书进行加密。在浏览器中检查您的网站是否已安装。

    m. 现在,certbot 在获得新证书时会为您设置自动续订,因此这是一项无需担心的任务。但为了确保它有效,您可以运行:

    sudo systemctl status certbot.timer

    现在,恭喜你!您已成功使用 Nginx 部署了 Web 应用程序。如果您想优化 Nginx,我建议您阅读这篇文章:基本 Nginx 设置。

    至此,我完成了关于**如何使用 Nginx 安全地部署 Node 应用程序**的文档。

    参考资料 - 更多信息

  • 服务器设置和强化
  • 服务器强化最佳实践
  • 服务器设置基础
  • 安装 fail2ban
  • Fail2ban Ubuntu 配置
  • 将 Node.js 部署到 VPS
  • Nginx 配置
  • 如何更改默认 SSH 端口
  • 简单的防火墙
  • DNS 备忘单
  • SCP
  • PM2 指南