服务器初始化
本文范围
这篇讲的是“新服务器交付给应用之前,系统基线要做到什么程度”。它解决的是账号、权限、时间、DNS、软件源、目录、磁盘、资源限制、防火墙、日志、监控、备份目录和内网离线依赖闭环,不负责替你把所有业务组件装完。
本文会覆盖:
- 拿到服务器后的只读巡检和初始化顺序。
- 部署用户、应用用户、sudoers、SSH 安全和回退入口。
- 主机名、时区、NTP、DNS、hosts、软件源和基础工具。
- 目录规划、磁盘挂载、文件句柄、进程数和 systemd 限制。
- firewalld、iptables/nftables、云安全组、日志轮转、监控 Agent、Docker 数据目录。
- 中国大陆公网、企业内网、完全离线环境里的依赖包、镜像、校验和验收清单。
本文不展开 Docker、Nginx、MySQL、Redis、MQ、K8S 的完整安装,也不讲云厂商网络架构、堡垒机产品选型或企业级 CMDB 流程。服务器初始化只做到可部署、可排障、可交接,后面的组件部署交给各自专题。
前置条件
你需要一台新交付或准备重建基线的 Linux 服务器,建议是仍在支持期内的 Ubuntu LTS、Debian、RHEL、Rocky Linux、AlmaLinux、CentOS Stream 或企业发行版。需要至少一个可登录账号,最好有 root 或受控 sudo 权限;如果是生产存量服务器,必须先备份关键配置并保留当前 SSH 会话。
开始前先确认:
whoami
id
cat /etc/os-release
uname -a
command -v sudo systemctl journalctl ss ip timedatectl chronyc firewall-cmd iptables nft如果服务器处在企业内网或离线环境,还要提前拿到内部 DNS、NTP、软件源、代理、镜像仓库、离线包清单和校验文件。本文示例里的用户、IP、域名和仓库地址都是占位,不要照抄成真实生产配置。
先把边界说清楚
拿到一台新 Linux 服务器,第一件事不是装 JDK、装 Docker、装 MySQL,而是先确认这台机器能不能稳定、安全、可追踪地承载部署。服务器初始化做的就是这件事:账号能登录,权限有边界,时间是准的,DNS 能解析,磁盘挂载清楚,目录有规划,端口放行说得明白,日志能保留,监控能接入,离线和内网也能继续交付。
这里不展开 Linux 完整教程,也不展开每个中间件的安装细节。Docker、Nginx、MySQL、Redis、MQ、K8S 都应该有自己的部署和治理手册。本文只做服务器交付前的基线,基线过了,后面的应用部署才不容易被系统问题拖住。
后面命令里会用几个占位,实际执行时替换成自己的值:
# 部署用户
deploy
# 应用运行用户
app
# 服务器内网地址
10.0.0.10
# 内网 DNS、NTP、软件源示例
dns.internal.example.com
ntp.internal.example.com
repo.internal.example.com这里要注意,初始化文档和脚本里不要写真实密码、真实 token、客户公网 IP、一次性密钥。能用变量就用变量,必须落盘的配置要控制权限。
初始化顺序
新服务器初始化一般按这个顺序走:
只读巡检
-> 用户和 sudo
-> SSH 安全
-> 主机名、时区、时间同步
-> DNS 和 hosts
-> 基础软件和软件源
-> 目录规划
-> 磁盘挂载
-> 文件句柄、进程数、systemd 限制
-> firewalld / iptables / nftables
-> 云厂商安全组
-> 日志和日志轮转
-> 监控 Agent
-> Docker 目录、数据目录、备份目录
-> 中国大陆 / 内网 / 离线依赖闭环
-> 初始化脚本
-> 验收清单这个顺序的核心是先确认,再修改;先保住登录入口,再收紧安全;先打好系统基线,再装业务组件。现场最怕的是边改 SSH、边改防火墙、边装应用,最后连不上机器,谁也说不清是哪一步弄坏的。
P1 图解:服务器初始化依赖顺序
初始化不是把命令从上到下跑一遍,而是把“后一步依赖前一步”的关系梳理清楚。下面这张图可以当作交付前的主线:任何会影响登录、磁盘、网络边界和审计能力的动作,都必须先有验证点,再进入下一步。
这张图的关键不是节点数量,而是依赖边界。SSH 安全 一定要在 保留登录入口 之后,不能先禁用密码登录再验证密钥;磁盘挂载 一定要在服务部署之前,不能先把应用跑起来再迁移数据目录;网络边界 一定要同时看本机防火墙和云安全组,不能只在控制台放行端口就认为 Linux 本机已经通了。
可以用下面这组命令做依赖验收:
cat /etc/os-release
id deploy
sudo sshd -t
timedatectl
getent hosts repo.internal.example.com
findmnt
ulimit -n
ss -lntup
journalctl --disk-usage常见坑有四类:第一,登录链路还没验证就改 sshd_config;第二,/etc/fstab 写完没有跑 mount -a,下一次重启才发现进不了系统;第三,只开云安全组,不看 firewalld、ufw、iptables 或 nftables;第四,把初始化脚本写成“全自动一键改完”,里面混入格式化磁盘、关闭密码登录、删除目录这类高风险步骤。
生产约束也要写进交付记录:高风险动作必须二次确认;所有修改要能回看命令和输出;内网、离线环境必须提前确认 DNS、NTP、软件源、镜像仓库和内部 CA;交付前至少留下一份只读巡检快照和一份最终验收清单。服务器初始化的目标不是“看起来干净”,而是下一位工程师接手时能知道它为什么这么配置。
初始化里最容易出事故的不是安装基础包,而是 SSH、sudoers、fstab、防火墙这几类会影响“还能不能回去修”的动作。下面这张回退链路要在正式脚本之外单独确认,不能藏进一键初始化。
这张图的底线是:任何会切断登录、提权、磁盘挂载或网络入口的动作,都要先有备份、验证和回退窗口。初始化脚本可以自动化安装工具、创建目录、写日志轮转,但不应该在未知机器状态下自动格式化磁盘、禁用密码登录或关闭端口。
第一步:只读巡检
拿到账号后,先做只读巡检。这个阶段不改配置,只回答几个问题:我是谁、系统是什么版本、资源够不够、磁盘怎么分、网卡和路由有没有问题、当前已经开了哪些端口。
whoami
id
pwd
cat /etc/os-release
uname -a
hostnamectl
lscpu
free -h
lsblk -f
df -hT
ip addr
ip route
ss -lntup这些命令怎么看:
whoami和id看当前账号是不是 root,属于哪些组,后面能不能 sudo。/etc/os-release看发行版和版本,比如 Ubuntu 22.04 / 24.04、Debian、RHEL 9、Rocky Linux、AlmaLinux、CentOS Stream。lscpu、free -h、lsblk -f、df -hT看 CPU、内存、块设备、文件系统和挂载点。ip route看默认路由,ss -lntup看当前监听端口和进程。
建议把初始化前快照保存下来:
mkdir -p /tmp/server-init-check
{
date
echo "## user"
whoami
id
echo "## os"
cat /etc/os-release
uname -a
hostnamectl
echo "## resource"
lscpu
free -h
lsblk -f
df -hT
echo "## network"
ip addr
ip route
ss -lntup
} | tee /tmp/server-init-check/before.txt如果输出里发现内存、磁盘、系统版本和交付单不一致,先停下来确认。比如业务要求单独数据盘,结果只有根分区;要求 Ubuntu 24.04,实际是 20.04;要求内网访问,默认路由却指向错误网段。这些问题越早发现越便宜。
用户和 sudo
生产服务器不建议长期用 root 做日常发布。比较稳的做法是 root 只负责初始化和紧急修复,部署用户负责发布,应用服务用户负责运行进程。这样出问题时能看清楚“谁改了配置、谁启动了服务、哪个进程占了资源”。
创建部署用户
Ubuntu / Debian:
sudo adduser deploy
sudo usermod -aG sudo deploy
id deployRHEL / Rocky / AlmaLinux:
sudo useradd -m -s /bin/bash deploy
sudo passwd deploy
sudo usermod -aG wheel deploy
id deploy这里的 deploy 是发布账号。执行完 id deploy 后,Ubuntu 系应该能看到 sudo 组,RHEL 系应该能看到 wheel 组。如果没有,说明用户还没有拿到 sudo 入口。
再开一个新的 SSH 会话验证,不要关掉当前 root 会话:
ssh deploy@10.0.0.10
sudo -v
sudo whoami正常情况下,sudo whoami 应该输出 root。如果提示 deploy is not in the sudoers file,说明 sudo 组或 sudoers 没配好;如果提示密码错误,先确认用户密码和键盘输入法;如果连 SSH 都进不去,先别改 root 登录策略。
用 sudoers.d 收敛权限
简单环境可以让部署用户拥有完整 sudo,生产环境更推荐把规则放到独立文件,便于审计和回滚。
sudo visudo -f /etc/sudoers.d/deploy示例配置:
deploy ALL=(ALL) ALL文件权限要收紧:
sudo chmod 0440 /etc/sudoers.d/deploy
sudo visudo -cvisudo -c 用来检查 sudoers 语法。这里要注意,不要直接用普通编辑器随手改 /etc/sudoers,语法写错可能导致所有 sudo 都不可用。真要限制到命令级,可以继续拆成允许重启指定服务、查看指定日志、执行指定发布脚本,这属于更细的权限治理。
创建应用服务用户
应用进程不要直接用部署用户跑,更不要用 root 跑。单独建一个无登录 shell 的服务用户:
sudo useradd -r -s /usr/sbin/nologin app 2>/dev/null || sudo useradd -r -s /sbin/nologin app
id app如果系统提示用户已存在,先看它是不是预期用户:
getent passwd app应用目录后面会给 app 用户授权。这样 Java 进程、Nginx worker、定时任务或后台服务出现文件权限问题时,能马上定位到运行用户,而不是所有东西都堆到 root 名下。
SSH 安全
SSH 是服务器的门。初始化时要先保证自己不会被锁在门外,再逐步收紧登录方式。实战里最稳的是:先配置密钥登录,开第二个窗口验证,再禁用危险项。
准备密钥登录
在本地生成密钥:
ssh-keygen -t ed25519 -C "deploy@server-init"如果已有团队密钥,可以直接使用团队密钥。把公钥放到服务器:
ssh-copy-id deploy@10.0.0.10没有 ssh-copy-id 时手工追加:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
vi ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys验证密钥登录:
ssh -i ~/.ssh/id_ed25519 deploy@10.0.0.10能免密登录以后,再看服务端权限:
ls -ld ~/.ssh
ls -l ~/.ssh/authorized_keys.ssh 一般是 700,authorized_keys 一般是 600。如果权限过宽,OpenSSH 可能直接忽略这个文件,表现就是明明放了公钥,登录时还是让你输密码。
调整 sshd_config
先备份:
sudo cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F-%H%M%S)编辑配置:
sudo vi /etc/ssh/sshd_config推荐基线:
PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin no
PermitEmptyPasswords no
X11Forwarding no
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2这几项分别解决什么问题:
PubkeyAuthentication yes:允许密钥登录。PasswordAuthentication no:关闭密码登录,降低爆破风险。PermitRootLogin no:禁止 root 直接远程登录,必须先登录普通用户再 sudo。PermitEmptyPasswords no:禁止空密码账号登录。MaxAuthTries 3:减少反复试密码的机会。ClientAliveInterval和ClientAliveCountMax:清理长期空闲会话。
改完先检查语法:
sudo sshd -t没有输出才是正常。然后重载或重启 SSH 服务:
# Ubuntu / Debian 常见服务名
sudo systemctl reload ssh || sudo systemctl restart ssh
# RHEL 系常见服务名
sudo systemctl reload sshd || sudo systemctl restart sshd这里要注意,别急着关闭当前窗口。新开一个终端重新连:
ssh -i ~/.ssh/id_ed25519 deploy@10.0.0.10
sudo whoami如果新窗口能登录,说明 SSH 基线基本安全。如果新窗口失败,当前窗口还在,可以立刻回滚:
sudo cp -a /etc/ssh/sshd_config.bak.YYYY-MM-DD-HHMMSS /etc/ssh/sshd_config
sudo sshd -t
sudo systemctl restart ssh || sudo systemctl restart sshdSSH 常见失败现象
Permission denied (publickey):优先查公钥是否放到目标用户的 ~/.ssh/authorized_keys,再查目录权限。
namei -l /home/deploy/.ssh/authorized_keys
sudo tail -n 100 /var/log/auth.log 2>/dev/null || sudo journalctl -u sshd -n 100 --no-pagerConnection timed out:通常不是账号问题,而是网络链路、宿主机防火墙或云安全组没放行。
nc -vz 10.0.0.10 22Connection refused:说明能到服务器,但 SSH 服务没监听或端口不对。
sudo systemctl status ssh 2>/dev/null || sudo systemctl status sshd
sudo ss -lntup | grep ':22'生产建议是保留堡垒机或管理网段入口,不要把 22 端口对全网开放。如果必须临时开放,也要有到期时间和回收记录。
主机名、时区和时间同步
主机名、时区、时间看起来小,但它们直接影响日志排查、证书校验、分布式锁、定时任务、审计记录。现场排障时,如果几台机器时间差几分钟,日志时间线就会很难对齐。
设置主机名
先看当前主机名:
hostnamectl
hostname -f设置主机名:
sudo hostnamectl set-hostname app-01
hostnamectlhostnamectl 输出里应该能看到 Static hostname: app-01。主机名建议带环境和角色,比如 prod-api-01、test-mq-01,不要所有机器都叫 localhost。
如果内网没有 DNS,还需要配 /etc/hosts:
sudo vi /etc/hosts示例:
10.0.0.10 app-01
10.0.0.11 app-02
10.0.0.20 db-01验证:
getent hosts app-01
ping -c 2 app-01getent hosts 能解析出 IP,说明系统解析链路可用。ping 不通不一定代表解析失败,可能是对方禁了 ICMP,所以先看 getent。
设置时区
查看当前时间和时区:
timedatectl
date中国大陆常用:
sudo timedatectl set-timezone Asia/Shanghai
timedatectl输出里应该看到:
Time zone: Asia/Shanghai如果是跨国业务,不要拍脑袋统一改时区。应用日志、数据库时区、JVM 时区、业务展示时区要一起约定。服务器层面统一时区只是第一步,真正上线还要检查应用参数。
配置时间同步
Ubuntu / Debian 可以先看系统自带同步:
timedatectl timesync-status 2>/dev/null || timedatectl
systemctl status systemd-timesyncd --no-pager 2>/dev/null启用:
sudo timedatectl set-ntp true
timedatectlRHEL 系常见用 chrony:
sudo systemctl enable --now chronyd
chronyc tracking
chronyc sources -v如果需要指定企业内网 NTP,编辑 chrony 配置:
sudo cp -a /etc/chrony.conf /etc/chrony.conf.bak.$(date +%F-%H%M%S) 2>/dev/null || true
sudo vi /etc/chrony.conf示例:
server ntp.internal.example.com iburst
makestep 1.0 3重启验证:
sudo systemctl restart chronyd
chronyc tracking
chronyc sources -v怎么看结果:
chronyc sources -v里有^*或^+,说明有可用时间源。chronyc tracking里的偏移量应该逐步收敛。timedatectl里System clock synchronized应该是yes或类似同步状态。
如果一直不同步,先查 123/udp 是否被防火墙或安全组拦住,再查内网 NTP 域名能不能解析。
getent hosts ntp.internal.example.com
sudo ss -lunp | grep ':123'DNS 和 hosts
DNS 不是只影响浏览器。拉软件包、拉镜像、访问注册中心、调用内网 API、连对象存储,都依赖 DNS。新服务器如果 DNS 配错,表现可能是 apt update 慢、docker pull 超时、应用启动时报 unknown host。
先看解析配置:
cat /etc/resolv.conf
resolvectl status 2>/dev/null || true
getent hosts repo.internal.example.comUbuntu Server 常见由 Netplan 和 systemd-resolved 管理,不建议直接手改 /etc/resolv.conf 后就当成永久配置。可以查看 Netplan 文件:
ls /etc/netplan
sudo vi /etc/netplan/01-netcfg.yaml示例:
network:
version: 2
ethernets:
eth0:
dhcp4: true
nameservers:
addresses:
- 10.0.0.2
- 10.0.0.3
search:
- internal.example.com应用配置:
sudo netplan try
sudo netplan apply
resolvectl status 2>/dev/null || cat /etc/resolv.confRHEL 系常见由 NetworkManager 管理:
nmcli device status
nmcli con show
sudo nmcli con mod "System eth0" ipv4.dns "10.0.0.2 10.0.0.3" ipv4.ignore-auto-dns yes
sudo nmcli con up "System eth0"实际连接名不一定叫 System eth0,用 nmcli con show 看到什么就改什么。
验证 DNS:
getent hosts repo.internal.example.com
curl -I --connect-timeout 5 http://repo.internal.example.com 2>/dev/nullgetent hosts 用系统解析链路,比单独 nslookup 更贴近应用真实行为。如果 getent 不通,但 nslookup 通,说明系统解析配置和工具配置可能不一致。
生产建议是:稳定的服务名放 DNS,少量固定机器或离线现场临时映射才放 /etc/hosts。不要让 hosts 文件变成“私有注册中心”,机器多了以后很难维护。
基础软件和软件源
基础软件只装初始化和排障需要的东西,不要一上来把所有组件都装满。一般需要这些:
curl、wget:下载和探测 HTTP。vim或nano:编辑配置。tar、unzip、gzip:解压安装包。lsof、net-tools、iproute2:查端口和网络。nc或nmap-ncat:测端口连通。jq:看 JSON。rsync:同步文件。logrotate:日志轮转。chrony:时间同步。
Ubuntu / Debian:
sudo apt update
sudo apt install -y curl wget vim tar unzip gzip lsof net-tools iproute2 netcat-openbsd jq rsync logrotate chrony ca-certificates gnupgRHEL / Rocky / AlmaLinux:
sudo dnf makecache
sudo dnf install -y curl wget vim tar unzip gzip lsof net-tools iproute nmap-ncat jq rsync logrotate chrony ca-certificatesCentOS 7 这类老系统可能还是 yum:
sudo yum makecache
sudo yum install -y curl wget vim tar unzip gzip lsof net-tools iproute nmap-ncat jq rsync logrotate chrony ca-certificates验证:
curl --version
nc -h 2>&1 | head
jq --version
systemctl status chronyd --no-pager 2>/dev/null || systemctl status chrony --no-pager 2>/dev/null || true如果 apt update 或 dnf makecache 很慢,先不要怪服务器性能,优先查 DNS、代理、软件源连通性。中国大陆公网、企业内网和离线现场,软件源必须单独处理,后面会专门讲。
目录规划
目录规划要在应用部署前定下来。否则后面日志写到根分区、Docker 写到默认目录、备份文件和业务数据混在一起,磁盘满的时候很难救。
常用规划:
/opt/software 安装包、离线包、二进制包
/opt/app 应用程序目录
/data 业务数据目录
/data/docker Docker 数据目录
/data/logs 应用日志目录
/backup 备份目录
/script 初始化、发布、巡检脚本创建目录:
sudo mkdir -p /opt/software /opt/app /data /data/docker /data/logs /backup /script
sudo chown -R deploy:deploy /opt/software /script
sudo chown -R app:app /opt/app /data/logs
sudo chmod 755 /opt /opt/software /opt/app /data /data/docker /data/logs /backup /script查看结果:
ls -ld /opt/software /opt/app /data /data/docker /data/logs /backup /script这里要注意,不要为了省事 chmod -R 777 /opt 或 chmod -R 777 /data。临时测试这么做很快,生产上会把权限边界打穿。真正需要写权限的目录,给对应用户或组;需要多人发布,就建发布组,不要全员写所有目录。
一个常见分工是:
deploy可以写/opt/software和/script。app可以写/opt/app、/data/logs和应用需要的数据目录。- 数据库、MQ、对象存储等组件使用各自用户和目录。
磁盘挂载
新服务器经常有一块系统盘,再挂一块数据盘。数据盘如果不挂载,后面 Docker、数据库、上传文件都写到根分区,根分区满了系统服务都会受影响。
识别磁盘
lsblk -f
sudo blkid
df -hT怎么看:
lsblk -f里没有挂载点、没有文件系统的新盘,可能就是待初始化数据盘。df -hT只显示已经挂载的文件系统。blkid能看到 UUID,后面写/etc/fstab推荐用 UUID。
假设数据盘是 /dev/vdb,先确认它没有重要数据:
sudo file -s /dev/vdb如果输出 data,通常表示还没有文件系统。如果输出 ext4、xfs 等信息,说明盘上已有文件系统,别直接格式化。
分区和格式化
新盘可以用 parted 分一个 GPT 分区:
sudo parted /dev/vdb --script mklabel gpt
sudo parted /dev/vdb --script mkpart primary 0% 100%
lsblk -f格式化,二选一:
sudo mkfs.xfs -f /dev/vdb1或:
sudo mkfs.ext4 -F /dev/vdb1一般 RHEL 系默认偏向 XFS,Ubuntu 上 ext4 和 XFS 都常见。数据库目录、对象存储目录、容器目录要结合组件官方建议和团队标准,不要现场随意混用。
挂载到 /data
创建挂载点并获取 UUID:
sudo mkdir -p /data
sudo blkid /dev/vdb1编辑 /etc/fstab:
sudo cp -a /etc/fstab /etc/fstab.bak.$(date +%F-%H%M%S)
sudo vi /etc/fstab示例:
UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /data xfs defaults,noatime,nofail 0 0如果格式化的是 ext4,就把第三列改成 ext4。
先测试挂载,不要直接重启:
sudo mount -a
df -hT /data
findmnt /data看到 /data 的文件系统类型和容量符合预期,才算成功。mount -a 如果报错,立刻检查 /etc/fstab 的 UUID、文件系统类型、空格分隔。fstab 写错可能导致重启进救援模式,所以这一步必须验证。
生产建议:
/data、/backup、数据库目录尽量不要和根分区混在一起。- Docker 的
data-root如果要迁移,必须在安装或上线前做,服务跑起来后再迁移风险更高。 - 备份目录最好和业务数据目录分盘,至少要有远端备份或对象存储兜底。
文件句柄、进程数和 systemd 限制
高并发服务、网关、长连接服务、数据库、MQ 都会吃文件句柄。每个 TCP 连接背后都有 socket,socket 也是文件描述符。默认 1024 对很多服务明显不够,现象可能是连接上不去、日志里出现 Too many open files。
先看当前限制:
ulimit -n
ulimit -u
cat /proc/sys/fs/file-max
cat /proc/sys/fs/file-nr解释一下:
ulimit -n是当前 shell 最大打开文件数。ulimit -u是当前用户最大进程数或线程数相关限制。fs.file-max是系统级文件句柄上限。fs.file-nr可以看当前已分配文件句柄数量。
给应用用户设置 limits:
sudo vi /etc/security/limits.d/90-app.conf示例:
app soft nofile 65535
app hard nofile 65535
app soft nproc 65535
app hard nproc 65535
deploy soft nofile 65535
deploy hard nofile 65535重新登录用户后验证:
su - deploy
ulimit -n
ulimit -u这里有一个坑:/etc/security/limits.d 对登录会话有效,但 systemd 启动的服务不一定吃这个配置。systemd 服务要单独在 unit 里设置。
示例:
sudo systemctl edit myapp.service写入:
[Service]
LimitNOFILE=65535
LimitNPROC=65535重载并验证:
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p LimitNOFILE -p LimitNPROC如果服务还没创建,也可以先把这个作为服务模板要求写进交付规范。
系统级句柄可以按需提高:
sudo vi /etc/sysctl.d/90-file.conf示例:
fs.file-max = 1000000应用:
sudo sysctl --system
cat /proc/sys/fs/file-max生产上不要盲目把所有值都调到百万级。文件句柄越大,只代表系统允许打开更多文件,不代表应用一定扛得住。线程数、内存、端口范围、数据库连接池、网关连接数要一起看。
防火墙、iptables 和安全组
端口不通时,不要只看一层。至少要查四层:服务有没有监听、本机防火墙有没有放行、云安全组有没有放行、对端网络有没有路由或 ACL。
先看服务是否监听
sudo ss -lntup
sudo ss -lntup | grep ':8080'如果服务没监听,防火墙放行也没用。这里要区分:
127.0.0.1:8080:只监听本机,外部访问不到。0.0.0.0:8080:监听所有 IPv4 地址。[::]:8080:监听 IPv6,是否同时支持 IPv4 要看系统配置。
本机测试:
curl -I http://127.0.0.1:8080
curl -I http://10.0.0.10:8080对端测试:
nc -vz 10.0.0.10 8080succeeded 或 open 才说明 TCP 端口通。timed out 多半是防火墙或网络拦截,refused 多半是服务没监听或监听地址不对。
firewalld
RHEL 系常用 firewalld。先看状态:
sudo systemctl status firewalld --no-pager
sudo firewall-cmd --state
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --list-all放行端口:
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports如果是标准服务,也可以按服务名放行:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
sudo firewall-cmd --list-services这里要注意 firewalld 有 runtime 和 permanent 两套配置。只执行 --add-port 不加 --permanent,当前生效但重启后丢;只改 permanent 不 reload,当前又不生效。所以生产上要么明确“临时验证”,要么永久配置后 reload 并复查。
iptables / nftables
有些系统不用 firewalld,或者底层已经切到 nftables。先看规则:
sudo iptables -S 2>/dev/null || true
sudo nft list ruleset 2>/dev/null | head -n 80 || true如果是老系统临时放行:
sudo iptables -I INPUT -p tcp --dport 8080 -j ACCEPT再从对端验证:
nc -vz 10.0.0.10 8080iptables 的持久化方式随发行版差异很大,不要只在命令行临时插一条规则就交付。正式环境要用 firewalld、nftables 配置文件,或团队统一的安全基线工具。
云安全组
云服务器还要看安全组。安全组是云厂商在实例网卡外面加的一层虚拟防火墙,本机 firewall-cmd --list-ports 放行了,不代表公网或 VPC 访问一定能进来。
排查顺序:
- 本机
ss -lntup确认服务监听。 - 本机
curl 127.0.0.1:端口确认服务响应。 - 同网段另一台机器
nc -vz 内网IP 端口。 - 云控制台检查安全组入方向规则。
- 如果走公网,再查公网 IP、负载均衡、NAT、云防火墙。
安全组规则建议:
- SSH 只放堡垒机、办公出口或管理网段,不要
0.0.0.0/0长期开 22。 - Web 入口按需开放 80/443。
- 数据库、Redis、MQ、注册中心默认只开放内网访问。
- 临时排障端口要写明申请人、原因、过期时间,到期回收。
日志和日志轮转
日志要在上线前想清楚。否则服务跑几天,/data/logs 或 /var/log 爆了,应用挂掉,SSH 也可能登录困难。
先看系统日志:
journalctl -p warning -n 100 --no-pager
du -sh /var/log
df -hT /var/log /data 2>/dev/null看 journald 占用:
journalctl --disk-usage可以给 journald 设置上限:
sudo mkdir -p /etc/systemd/journald.conf.d
sudo vi /etc/systemd/journald.conf.d/90-size.conf示例:
[Journal]
SystemMaxUse=1G
RuntimeMaxUse=256M
MaxRetentionSec=30day重启:
sudo systemctl restart systemd-journald
journalctl --disk-usage应用日志建议放到 /data/logs/应用名,再配置 logrotate:
sudo mkdir -p /data/logs/myapp
sudo chown -R app:app /data/logs/myapp
sudo vi /etc/logrotate.d/myapp示例:
/data/logs/myapp/*.log {
daily
rotate 30
missingok
notifempty
compress
delaycompress
copytruncate
create 0640 app app
}验证 logrotate 配置:
sudo logrotate -d /etc/logrotate.d/myapp-d 是调试模式,不会真正轮转。确认没报错后,可以在低峰期手动触发一次:
sudo logrotate -f /etc/logrotate.d/myapp这里要注意,copytruncate 适合不能重新打开日志文件的老应用,但可能丢极少量日志。更好的方式是应用支持按天滚动,或者收到信号后重新打开日志。日志采集 Agent 也要配合轮转策略,不要一轮转就丢采集偏移。
监控 Agent 初始化
服务器交付前至少要预留监控入口。无论你用 Prometheus Node Exporter、云厂商 Agent、Zabbix Agent,还是自研采集器,都要能看到 CPU、内存、磁盘、网络、进程、端口、时间同步和文件系统使用率。
以 Node Exporter 为例,常见监听端口是 9100。安装方式各团队不同,这里只放基线检查:
ss -lntup | grep ':9100' || true
systemctl status node_exporter --no-pager 2>/dev/null || true如果已经启动,访问应该能看到指标文本:
curl -s http://127.0.0.1:9100/metrics | head看到 node_cpu_seconds_total、node_filesystem_avail_bytes 这类指标,说明采集端至少能出数据。然后从监控服务器再访问一次:
curl -s http://10.0.0.10:9100/metrics | head如果本机能访问、监控服务器不能访问,优先查 firewalld、安全组、路由和监控网段 ACL。
生产建议:
- Agent 尽量只对监控网段开放,不要对公网开放。
- Agent 配置纳入初始化脚本或 CMDB,不要靠手工登记。
- 告警至少覆盖磁盘使用率、inode、CPU、内存、时间同步、关键端口、关键进程。
Docker 目录、数据目录和备份目录
即使这台服务器暂时不装 Docker,也建议提前规划 Docker 数据目录。Docker 默认数据目录通常在 /var/lib/docker,如果根分区比较小,镜像层、容器日志、可写层很快会把根分区打满。
预留目录:
sudo mkdir -p /data/docker /data/app-data /backup
sudo chmod 755 /data/docker /data/app-data /backup如果已经安装 Docker,先看当前配置:
docker info 2>/dev/null | grep -E 'Docker Root Dir|Logging Driver|Storage Driver' || true推荐在 Docker 正式使用前配置 data-root 和日志轮转:
sudo mkdir -p /etc/docker
sudo vi /etc/docker/daemon.json示例:
{
"data-root": "/data/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}应用:
sudo dockerd --validate --config-file=/etc/docker/daemon.json 2>/dev/null || true
sudo systemctl daemon-reload
sudo systemctl restart docker
docker info | grep -E 'Docker Root Dir|Logging Driver|Storage Driver'如果 Docker Root Dir 变成 /data/docker,说明生效。这里要注意,已有容器和镜像的机器迁移 data-root 不能只改配置重启,旧数据不会自动搬过去。生产上要先停服务、备份、迁移、验证,再开放业务。
数据目录和备份目录也要提前分清:
/data/app-data/myapp 应用上传文件、运行数据
/data/logs/myapp 应用日志
/backup/myapp 应用备份
/backup/db 数据库备份备份不是把文件复制到同一块盘的另一个目录就完事。至少要考虑远端同步、对象存储、备份校验和恢复演练。初始化阶段先把目录和权限留好,后续备份专题再展开策略。
中国大陆、企业内网和离线环境
国内公网、企业内网、离线现场是服务器初始化里最容易被低估的部分。很多教程默认能访问海外仓库、Docker Hub、GitHub,但真实交付现场经常不行。这里要提前做依赖闭环。
中国大陆公网
先检查系统源可用性:
sudo apt update或:
sudo dnf makecache如果慢或超时,可以换成云厂商官方源、发行版镜像站或企业统一源。不要在正式文档里只写一个不稳定的第三方地址作为唯一方案。换源后要验证:
apt-cache policy curl 2>/dev/null || dnf repolistDocker 镜像拉取也要校准。公共镜像站、加速器、个人镜像服务可用性变化很快,生产更推荐:
- 使用云厂商容器镜像服务。
- 使用企业 Harbor / Nexus / Artifactory。
- 对基础镜像做版本固定和安全扫描。
- 在发布前把镜像同步到内网仓库。
验证镜像链路:
docker pull registry.internal.example.com/base/nginx:1.27
docker image inspect registry.internal.example.com/base/nginx:1.27 >/dev/null能拉下来并能 inspect,才说明当前节点具备镜像拉取条件。
企业内网
企业内网一般要先确认四件事:DNS、NTP、软件源、代理。
getent hosts repo.internal.example.com
getent hosts ntp.internal.example.com
curl -I --connect-timeout 5 http://repo.internal.example.com
chronyc sources -v 2>/dev/null || timedatectl如果出公网必须走代理,临时验证可以这样:
export http_proxy=http://proxy.internal.example.com:8080
export https_proxy=http://proxy.internal.example.com:8080
curl -I https://example.com生产脚本里不要把代理账号密码明文写死。更好的方式是由系统环境、CI/CD 凭据、包管理器配置或专门的代理网关统一管理。
内网源至少要覆盖:
- OS 包:apt / dnf / yum。
- 语言依赖:Maven、npm、pip、Go module。
- 容器镜像:基础镜像、中间件镜像、业务镜像。
- 安装包:JDK、Nginx、数据库客户端、监控 Agent。
每次初始化不要只问“能不能上网”,要问“业务上线需要的依赖是不是都能从内网拿到”。
完全离线环境
离线现场要提前准备离线包清单。基本思路是:在有网机器下载,在离线机器校验和安装。
Ubuntu / Debian 下载包:
mkdir -p offline-debs
sudo apt update
apt download curl wget vim jq rsync logrotate chrony更完整的依赖下载可以使用专门的离线仓库工具或 apt-offline。RHEL 系可以用:
mkdir -p offline-rpms
sudo dnf download --resolve --destdir=offline-rpms curl wget vim jq rsync logrotate chrony在有网机器生成校验文件:
sha256sum offline-rpms/* > SHA256SUMS拷贝到离线机器后验证:
sha256sum -c SHA256SUMS安装:
sudo dnf install -y ./offline-rpms/*.rpm或:
sudo dpkg -i ./offline-debs/*.deb
sudo apt -f installDocker 镜像离线迁移:
docker pull registry.internal.example.com/base/nginx:1.27
docker save registry.internal.example.com/base/nginx:1.27 -o nginx-1.27.tar
sha256sum nginx-1.27.tar > nginx-1.27.tar.sha256离线机器导入:
sha256sum -c nginx-1.27.tar.sha256
docker load -i nginx-1.27.tar
docker images | grep nginx这里要注意,离线环境不是把几个 rpm、deb、tar 包拷过去就完事。还要有版本清单、校验和、安装顺序、回滚包、漏洞修复路径。没有这些,第一次能装起来,第二次很可能复现不了。
初始化脚本骨架
手工命令跑通以后,再沉淀成脚本。脚本要幂等,重复执行不会把机器改坏;脚本要有日志,失败时知道卡在哪一步;脚本要有确认变量,不要把生产机器写死。
示例骨架:
#!/usr/bin/env bash
set -euo pipefail
DEPLOY_USER="${DEPLOY_USER:-deploy}"
APP_USER="${APP_USER:-app}"
TIMEZONE="${TIMEZONE:-Asia/Shanghai}"
HOSTNAME_VALUE="${HOSTNAME_VALUE:-app-01}"
LOG_FILE="${LOG_FILE:-/tmp/server-init.log}"
log() {
echo "[$(date '+%F %T')] $*" | tee -a "$LOG_FILE"
}
require_root() {
if [ "$(id -u)" -ne 0 ]; then
echo "please run as root or sudo" >&2
exit 1
fi
}
create_user_if_missing() {
local user="$1"
local shell="$2"
if id "$user" >/dev/null 2>&1; then
log "user exists: $user"
else
useradd -m -s "$shell" "$user"
log "created user: $user"
fi
}
main() {
require_root
log "set hostname"
hostnamectl set-hostname "$HOSTNAME_VALUE"
log "set timezone"
timedatectl set-timezone "$TIMEZONE"
timedatectl set-ntp true || true
log "create users"
create_user_if_missing "$DEPLOY_USER" /bin/bash
if getent group sudo >/dev/null; then
usermod -aG sudo "$DEPLOY_USER"
elif getent group wheel >/dev/null; then
usermod -aG wheel "$DEPLOY_USER"
fi
if ! id "$APP_USER" >/dev/null 2>&1; then
useradd -r -s /usr/sbin/nologin "$APP_USER" 2>/dev/null || useradd -r -s /sbin/nologin "$APP_USER"
fi
log "create directories"
mkdir -p /opt/software /opt/app /data/docker /data/logs /backup /script
chown -R "$DEPLOY_USER:$DEPLOY_USER" /opt/software /script
chown -R "$APP_USER:$APP_USER" /opt/app /data/logs
chmod 755 /opt/software /opt/app /data /data/docker /data/logs /backup /script
log "write limits"
cat >/etc/security/limits.d/90-app.conf <<EOF
$APP_USER soft nofile 65535
$APP_USER hard nofile 65535
$APP_USER soft nproc 65535
$APP_USER hard nproc 65535
$DEPLOY_USER soft nofile 65535
$DEPLOY_USER hard nofile 65535
EOF
log "done"
}
main "$@"执行:
sudo DEPLOY_USER=deploy APP_USER=app HOSTNAME_VALUE=app-01 bash server-init.sh执行后要看日志:
tail -n 100 /tmp/server-init.log脚本里没有直接改 SSH 和磁盘挂载,是故意的。SSH 和磁盘属于高风险操作,建议先手工验证,再把适合团队环境的部分固化进脚本。尤其是禁用密码登录、格式化磁盘、写 fstab,这些命令不应该在不知道目标机器状态时无脑执行。
验收清单
初始化完成后,不要靠感觉交付。按清单逐项验收。
系统和资源
cat /etc/os-release
hostnamectl
lscpu
free -h
df -hT
lsblk -f验收点:
- 系统版本符合交付要求。
- CPU、内存、磁盘容量符合规格。
/data、/backup等挂载点正确。- 根分区剩余空间充足。
用户和 SSH
id deploy
id app
sudo -l -U deploy
sudo sshd -t
ss -lntup | grep ':22'验收点:
deploy可以登录并 sudo。app是应用运行用户,不用于远程登录。- 密钥登录可用。
- root 远程登录、密码登录策略符合安全要求。
- 新 SSH 会话验证通过后,才关闭旧会话。
时间和 DNS
timedatectl
chronyc tracking 2>/dev/null || true
chronyc sources -v 2>/dev/null || true
getent hosts repo.internal.example.com
getent hosts ntp.internal.example.com验收点:
- 时区正确。
- 时间同步正常。
- 内网域名能解析。
- 软件源、NTP、镜像仓库域名能解析。
软件源和基础软件
sudo apt update 2>/dev/null || sudo dnf makecache
curl --version
jq --version
nc -h 2>&1 | head验收点:
- 包管理器能更新索引。
- 基础排障工具已安装。
- 内网或离线源有记录,不能只依赖临时公网。
目录和磁盘
ls -ld /opt/software /opt/app /data /data/docker /data/logs /backup /script
findmnt /data
sudo mount -a验收点:
- 目录存在。
- 权限属于预期用户。
mount -a不报错。/etc/fstab使用 UUID 或稳定设备标识。
文件句柄和进程数
su - deploy -c 'ulimit -n && ulimit -u'
cat /proc/sys/fs/file-max验收点:
- 部署用户和应用用户的限制符合服务要求。
- systemd 服务模板里有
LimitNOFILE等配置要求。 - 没有盲目设置过大值而缺少容量评估。
防火墙和安全组
sudo firewall-cmd --state 2>/dev/null || true
sudo firewall-cmd --list-all 2>/dev/null || true
sudo iptables -S 2>/dev/null || true
sudo nft list ruleset 2>/dev/null | head -n 80 || true
ss -lntup验收点:
- 本机开放端口和服务监听一致。
- 安全组入方向只开放必要来源和端口。
- SSH、数据库、Redis、MQ 这类敏感端口没有长期全网开放。
- 临时端口有回收记录。
日志、监控和备份
journalctl --disk-usage
ls /etc/logrotate.d
ss -lntup | grep -E ':9100|:10050' || true
df -hT /data /backup 2>/dev/null验收点:
- journald 有容量限制。
- 应用日志目录和轮转策略已准备。
- 监控 Agent 已安装或预留接入方案。
- 备份目录存在,远端备份方案有后续交付项。
常见故障排查
| 现象 | 优先排查 | 常用命令 | 处理思路 |
|---|---|---|---|
| SSH 连不上,超时 | 网络、防火墙、安全组 | nc -vz IP 22、firewall-cmd --list-all | 先确认端口链路,再看 SSH 服务 |
| SSH 提示 publickey 失败 | 公钥、权限、用户目录 | namei -l ~/.ssh/authorized_keys、journalctl -u sshd | 修正 .ssh 权限和 authorized_keys |
| sudo 不可用 | sudoers 语法、用户组 | id deploy、visudo -c | 用 root 会话修复 sudoers |
| 时间不同步 | NTP、DNS、UDP 123 | chronyc sources -v、timedatectl | 指定内网 NTP,检查防火墙 |
| apt / dnf 很慢 | DNS、源、代理 | getent hosts、curl -I 源地址 | 换企业源或配置代理 |
| 端口本机通,对端不通 | 监听地址、防火墙、安全组 | ss -lntup、nc -vz | 按服务、本机防火墙、安全组逐层排查 |
| 根分区满 | 日志、Docker、临时文件 | du -xh / --max-depth=1、docker system df | 清理日志,迁移数据目录,补轮转 |
| Too many open files | nofile、systemd 限制 | ulimit -n、systemctl show 服务 -p LimitNOFILE | 同时改登录限制和 systemd unit |
| mount -a 报错 | fstab UUID、文件系统类型 | blkid、findmnt | 修正 fstab,先测试再重启 |
| 监控无数据 | Agent、端口、安全组、采集配置 | curl :9100/metrics、systemctl status | 本机先通,再查监控端到服务器链路 |
排障时不要只盯着最后一个报错。比如应用端口不通,先看进程监听,再看本机访问,再看对端访问,最后看安全组。层次清楚,问题会快很多。
常用命令速查
系统:
cat /etc/os-release
uname -a
hostnamectl
lscpu
free -h
df -hT
lsblk -f网络:
ip addr
ip route
ss -lntup
nc -vz 10.0.0.10 8080
curl -I http://10.0.0.10:8080
getent hosts repo.internal.example.com用户:
id deploy
sudo -l -U deploy
getent passwd appSSH:
sudo sshd -t
sudo systemctl status ssh --no-pager 2>/dev/null || sudo systemctl status sshd --no-pager
sudo tail -n 100 /var/log/auth.log 2>/dev/null || sudo journalctl -u sshd -n 100 --no-pager时间:
timedatectl
chronyc tracking 2>/dev/null || true
chronyc sources -v 2>/dev/null || true防火墙:
sudo firewall-cmd --state
sudo firewall-cmd --list-all
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload日志:
journalctl -p warning -n 100 --no-pager
journalctl --disk-usage
sudo logrotate -d /etc/logrotate.d/myapp限制:
ulimit -n
ulimit -u
cat /proc/sys/fs/file-max
systemctl show myapp.service -p LimitNOFILE -p LimitNPROC生产建议
服务器初始化最后要留下三样东西:初始化记录、可复用脚本、验收结果。没有记录,后面出了问题只能猜;没有脚本,下一台机器还要靠手敲;没有验收,谁也不知道这台机器到底能不能交付。
落地工程深水区
服务器初始化最容易被低估。很多团队把它当成“装几条基础命令”,结果真正上线后被登录权限、磁盘目录、时间漂移、软件源不可达、文件句柄和安全组拖住。
第一是基线漂移。今天手工改一台,明天脚本改一台,三个月后同一个服务在不同机器上跑出来的环境都不一样。初始化完成后要留下基线快照和验收结果:
cat /etc/os-release
hostnamectl
timedatectl
lsblk -f
df -hT
systemctl list-unit-files --type=service | grep -E 'ssh|chrony|docker|node_exporter'第二是登录入口和 sudo 权限。SSH 安全不能只追求“禁用密码登录”,更要保证禁用前至少有一条密钥链路已验证、有应急账号、有变更回退窗口。sudoers.d 只放必要命令,改完用 visudo -cf 检查,避免一行错误导致全体无法提权。
第三是数据目录和磁盘边界。Docker data-root、应用目录、日志目录、备份目录、数据库目录不要全挤在根分区。上线前要确认哪些目录在独立数据盘,哪些目录会增长,哪些目录可以清理:
findmnt
df -hT / /data /opt /var
du -xhd1 /var /opt 2>/dev/null | sort -h第四是内网依赖闭环。企业内网和离线环境里,DNS、NTP、软件源、镜像仓库、CA 证书、代理都要在初始化阶段定好。不要让应用发布脚本临时发现 apt update 失败、镜像拉不到、证书链不信任。离线包要有版本清单、安装顺序、校验和和回滚包。
第五是团队职责。初始化不是某个人登录机器随手做完就结束,要明确谁负责系统基线、谁负责云安全组、谁负责软件源、谁负责监控、谁负责备份恢复。机器交付时至少输出:初始化记录、差异说明、开放端口、sudo 权限、磁盘规划、监控状态、备份路径、未完成风险。没有这份交接,后续任何部署事故都会变成追忆现场。
生产上建议固定这些约束:
- 每台机器交付前必须有只读巡检快照。
- SSH 调整必须保留一个已验证会话,禁止边改边断。
- 目录、磁盘、Docker data-root 在部署前定好,上线后少迁移。
- 端口开放必须同时记录本机防火墙和云安全组。
- 日志必须有轮转,监控必须能采集,备份必须能恢复。
- 中国大陆、企业内网、离线环境必须提前准备软件源、镜像仓库和离线包。
- 初始化脚本只自动化低风险、可幂等步骤;格式化磁盘、禁用密码登录这类高风险步骤要显式确认。
一台服务器能不能部署,不是看能不能把应用进程启动起来,而是看它出了问题以后能不能查、能不能修、能不能复现、能不能交接。初始化基线做扎实,后面部署中间件和应用会轻松很多。
