Linux - 编程·投资·科技 https://www.devlearn.club/posts/category/linux Mon, 01 Jun 2026 01:08:15 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0 https://www.devlearn.club/wp-content/uploads/2020/04/cropped-icon-32x32.png Linux - 编程·投资·科技 https://www.devlearn.club/posts/category/linux 32 32 Linux find命令用法详解:文件查找的12个实战场景(2026) https://www.devlearn.club/posts/692 Mon, 01 Jun 2026 01:07:38 +0000 https://www.devlearn.club/posts/692 前言:为什么每个运维都必须精通 find…

Linux find命令用法详解:文件查找的12个实战场景(2026)最先出现在编程·投资·科技

]]>
前言:为什么每个运维都必须精通 find?

如果你问一个 Linux 老手「你最常用的命令是什么」,find 大概率排在前三。它不是 ls 那种「看一眼」的工具,而是真正的「搜索引擎」——按名称、大小、时间、权限、所有者、文件类型……几乎所有维度,它都能精准定位。

但现实中,很多人对 find 的使用停留在 find . -name "*.log" 这个层次。本文从 12 个真实生产场景出发,带你从入门到精通。

测试环境:Ubuntu 24.04 LTS,find 版本 4.9.0(GNU findutils)。所有示例可直接复制运行。

一、基础篇:find 的核心语法

$ find [路径] [表达式] [动作]

如果省略路径,默认从当前目录 . 开始。如果省略动作,默认执行 -print,即打印匹配的文件路径。

$ find /var/log -name "*.log"
/var/log/syslog
/var/log/auth.log
/var/log/kern.log

二、12 个生产环境实战场景

场景 1:按文件名查找

最常见的用法——在 /etc/nginx 下找所有 .conf 文件:

$ find /etc/nginx -name "*.conf"
/etc/nginx/nginx.conf
/etc/nginx/sites-available/default
/etc/nginx/conf.d/ssl.conf

如果需要忽略大小写,用 -iname

$ find /var/log -iname "*.LOG"

场景 2:按文件类型查找

-type 是最实用的过滤条件之一:

选项 含义
f 普通文件
d 目录
l 符号链接
s socket
$ find /etc -type d -name "nginx"
/etc/nginx

找当前目录下所有空目录:

$ find . -type d -empty

场景 3:按文件大小查找

运维排障的救命稻草——磁盘满了,谁在吃空间?

$ find /var/log -type f -size +100M
/var/log/journal/d7b3.../system.journal

大小单位对照:

后缀 含义 示例
c 字节 -size +1024c
k KB -size +500k
M MB -size +100M
G GB -size +1G

注意:+100M 是大于 100MB,-100M 是小于 100MB,100M 是精确 100MB(极少用到)。

场景 4:按修改时间查找

「昨天谁改了这个配置文件?」——-mtime 告诉你答案。

$ find /etc/nginx -name "*.conf" -mtime -1
/etc/nginx/sites-available/default

时间参数速查:

参数 含义 示例
-mtime -1 最近 24 小时内修改 排查谁动了配置
-mtime +7 7 天前修改 找旧日志/缓存
-mmin -30 最近 30 分钟内修改 实时监控
-atime +30 30 天内未访问 清理冷数据
-ctime -1 24h 内元数据变更 权限/所有者变化

清理 30 天前的临时文件:

$ find /tmp -type f -mtime +30 -delete

⚠ 警告:-delete 不可逆!建议先用 -print 预览,确认无误再执行。

场景 5:按权限查找

安全审计必备——找出权限过于宽松的文件:

$ find /var/www -type f -perm 777
$ find /etc -type f -perm /o+w 2>/dev/null

-perm /o+w 匹配任何「其他人可写」的文件,2>/dev/null 屏蔽权限不足的错误提示。

场景 6:按所有者查找

$ find /var/www -user www-data
$ find /home -group developers

找出不属于任何用户的孤儿文件:

$ find /srv -nouser

场景 7:多条件组合(AND / OR)

find 默认是 AND 逻辑,用 -o 实现 OR,用 ( ) 分组:

$ find /var/log \( -name "*.log" -o -name "*.gz" \) -mtime -1

等价于:找最近 1 天内修改的 .log .gz 文件。

场景 8:对匹配文件执行操作(-exec)

这可能是 find 最强大的能力——找到文件后,立即对它做点什么。

批量修改权限:

$ find /var/www -type f -name "*.sh" -exec chmod +x {} \;

批量压缩旧日志:

$ find /var/log -type f -name "*.log" -mtime +30 -exec gzip {} \;

{} 是匹配文件名的占位符,\; 表示命令结束。如果想把多个文件一次性传给命令,用 + 替代 \;

$ find /var/log -name "*.log" -exec du -sh {} +

场景 9:配合 xargs 处理海量文件

当匹配文件数量极大(数万、数十万),-exec 每次启动一个进程,效率低。xargs 批量处理:

$ find /data -type f -name "*.tmp" -print0 | xargs -0 rm -f

-print0-0 配合,安全处理文件名中包含空格、换行等特殊字符的情况。

场景 10:按深度限制搜索范围

$ find / -maxdepth 3 -name "nginx.conf" 2>/dev/null
/etc/nginx/nginx.conf
/usr/share/doc/nginx-common/examples/nginx.conf

-maxdepth 限制向下搜索层数,-mindepth 限制起始层数:

$ find /var/log -mindepth 2 -type f

跳过 /var/log 本身,只搜子目录里的文件。

场景 11:排除指定目录

$ find / -path /proc -prune -o -path /sys -prune -o -name "*.conf" -print 2>/dev/null

-prune 排除指定路径,避免扫描 /proc/sys 等虚拟文件系统。

场景 12:按 inode 查找与去重

找硬链接——两个文件名指向同一个 inode:

$ find /home -type f -links +1

按 inode 号查找(当你需要删除文件名包含奇怪字符的文件时):

$ ls -i  # 先找到 inode 号
$ find . -inum 458752 -delete

三、find 与其他命令的黄金组合

find + grep:搜文件内容

$ find /etc/nginx -name "*.conf" -exec grep -l "proxy_pass" {} \;

在所有 nginx 配置文件中搜包含 proxy_pass 的。

find + stat:批量查看文件详情

$ find /var/log -name "*.log" -mtime -1 -exec stat --format="%n %s %y" {} \;

find + tar:打包指定时间范围的文件

$ find /var/log -name "*.log" -mtime -7 | tar -czf weekly_logs.tar.gz -T -

四、性能优化建议

  1. 指定具体路径:find /var/logfind / 快几百倍。
  2. 类型过滤放前面:-type f -name "*.log" 先筛掉目录,减少后续匹配量。
  3. 减少 -exec 调用次数:+ 替代 \;,或用 xargs
  4. 合理使用 prune:排除不关心的目录,如 /proc/sys.git
  5. 善用 maxdepth:不需要深层递归时设置 -maxdepth 3

五、速查表

需求 命令
按名称找文件 find /path -name "*.log"
忽略大小写 find /path -iname "*.LOG"
找大文件(>100MB) find /path -type f -size +100M
找空文件/目录 find /path -empty
24h内修改的文件 find /path -mtime -1
30天前的文件 find /path -mtime +30
权限为777的文件 find /path -perm 777
排除目录 find / -path /proc -prune -o ...
限制深度 find /path -maxdepth 2
批量删除 find /path -name "*.tmp" -delete
批量执行命令 find /path -name "*.sh" -exec chmod +x {} \;

常见问题 FAQ

find 和 locate 有什么区别?什么时候用哪个?

locate 依赖预先构建的文件名数据库(updatedb),速度极快但结果可能过时(数据库通常每天更新一次)。find 实时扫描文件系统,结果精确但速度慢。简单规则:找文件名用 locate(毫秒级),需要按大小/时间/权限过滤用 find

find -exec 中的 {} 和 \; 是什么意思?

{} 是匹配到的文件名占位符,每条匹配结果会替换到这里。\; 是命令结束标记(分号被转义,防止被 shell 解释)。如果想让多个匹配文件一次性传给命令(而不是每个文件启动一次进程),用 + 代替 \;

如何安全地使用 find -delete?

三步法:① 先不加 -delete,用 -print 预览结果;② 把结果输出到文件,人工抽查;③ 确认无误后加上 -delete。或在最后确认前改用 -exec rm -i {} \; 逐个确认删除。另一个好习惯:先用 -maxdepth 限制范围测试。


延伸阅读:

Linux find命令用法详解:文件查找的12个实战场景(2026)最先出现在编程·投资·科技

]]>
Linux磁盘管理完全指南:分区、挂载与LVM扩容实战(2026) https://www.devlearn.club/posts/641 Fri, 29 May 2026 01:04:25 +0000 https://www.devlearn.club/posts/641 前言:为什么你必须学会磁盘管理 你有没有…

Linux磁盘管理完全指南:分区、挂载与LVM扩容实战(2026)最先出现在编程·投资·科技

]]>
前言:为什么你必须学会磁盘管理

你有没有遇到过这种情况——VPS 跑着跑着突然服务挂了,一查才发现 /var 分区满了?MySQL 写不进去数据、Nginx 报 500、Docker 容器莫名其妙退出……根因都是磁盘满了。

我刚用 VPS 那会儿就踩过这个坑。WordPress 站点突然打不开,SSH 上去 df -h 一看,/ 分区使用率 100%。当时满脑子「怎么办怎么办」,最后只能删日志、清缓存临时续命。后来升级了 VPS 磁盘,发现新加的磁盘在系统里根本看不到——因为没分区、没格式化、没挂载。

这就是今天这篇文章要解决的问题。我会从头讲 Linux 磁盘管理的完整链路:查看磁盘 → 分区 → 格式化 → 挂载 → 永久挂载,以及进阶的 LVM 逻辑卷管理 + 在线扩容。读完你会发现这玩意儿没想象中那么可怕。

本文基于 Ubuntu 24.04 LTS,大部分命令在 Debian/CentOS/RHEL 上通用,差异处会单独标注。

第一步:查看磁盘信息 — 先搞清楚你有多少盘

在动手之前,先摸清家底。三个命令搞定:

1. lsblk — 最直观的磁盘树状图

$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sda      8:0    0   50G  0 disk
├─sda1   8:1    0   49G  0 part /
├─sda14  8:14   0    4M  0 part
└─sda15  8:15   0  106M  0 part /boot/efi
sdb      8:16   0  100G  0 disk

sda 是系统盘(已分区),sdb 是刚加的数据盘——100G,赤裸裸一整块,没有分区。这就是我们要操作的对象。

2. fdisk -l — 传统分区表查看

$ sudo fdisk -l /dev/sdb
Disk /dev/sdb: 100 GiB, 107374182400 bytes, 209715200 sectors
Disk model: QEMU HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

确认 /dev/sdb 没有分区表,可以放心操作。

3. df -h — 查看已挂载分区的使用情况

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        49G   38G  8.6G  82% /
tmpfs           1.9G  1.1M  1.9G   1% /dev/shm

82% 的使用率还算安全,但如果超过 90% 就该警惕了。

第二步:传统分区 — fdisk 分区 + 格式化 + 挂载

如果不打算用 LVM(后面会讲),这条链路就是最标准的操作流程。以 /dev/sdb 这个 100G 空盘为例。

2.1 创建分区

$ sudo fdisk /dev/sdb

Welcome to fdisk (util-linux 2.39.3).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Command (m for help): n        # 新建分区
Partition type: p (primary)    # 主分区
Partition number: 1
First sector: 2048             # 默认
Last sector: +100G             # 或用回车使用全部空间

Command (m for help): w        # 写入并退出
The partition table has been altered.
Syncing disks.

再跑一次 lsblk,你会看到 sdb1 出现了。

$ lsblk /dev/sdb
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sdb      8:16   0  100G  0 disk
└─sdb1   8:17   0  100G  0 part

2.2 格式化 — 创建文件系统

分区只是划了块地,文件系统才是决定「怎么存」的。Ubuntu 上首选 ext4:

$ sudo mkfs.ext4 /dev/sdb1
mke2fs 1.47.0 (5-Feb-2023)
Creating filesystem with 26214400 4k blocks and 6553600 inodes
Filesystem UUID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
        4096000, 7962624, 11239424, 20480000, 23887872

Allocating group tables: done
Writing inode tables: done
Creating journal (131072 blocks): done
Writing superblocks and filesystem accounting information: done

格式选择建议

  • ext4:最通用,稳定可靠,单文件最大 16TB
  • XFS:CentOS/RHEL 默认,大文件性能好,但不支持缩容
  • btrfs:支持快照和压缩,适合折腾党

2.3 挂载 — 让系统能访问

$ sudo mkdir -p /data
$ sudo mount /dev/sdb1 /data

$ df -h /data
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb1        98G   24K   93G   1% /data

挂载成功。但有个问题:重启后这个挂载就没了。因为 mount 命令只是临时的。

2.4 永久挂载 — 写入 /etc/fstab

/etc/fstab 是系统启动时自动挂载的配置文件。这一步操作不当会导致系统无法启动,务必小心。

先用 blkid 获取分区的 UUID(推荐用 UUID 而非设备名,因为设备名可能变化):

$ sudo blkid /dev/sdb1
/dev/sdb1: UUID="a1b2c3d4-e5f6-7890-abcd-ef1234567890" TYPE="ext4"

/etc/fstab 末尾追加一行:

$ echo 'UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890 /data ext4 defaults 0 2' | sudo tee -a /etc/fstab

验证 fstab 是否写对(不会真的挂载,只做语法检查):

$ sudo mount -a
# 没有报错 = 没问题

重启后 df -h 确认 /data 还在,就大功告成了。

第三步:LVM 逻辑卷管理 — 弹性伸缩的王牌

传统分区有个致命缺点:一旦分好就不能在线扩容。比如你把 /var 分了 20G,半年后满了,要么迁移数据,要么停机重分区——都是一身汗。

LVM(Logical Volume Manager)就是来解决这个问题的。它在你和物理磁盘之间加了一个抽象层,让你可以随时加减空间。

LVM 三层架构

先理解这三个概念,后面的操作就顺了:

  • PV(Physical Volume,物理卷):你的真实磁盘或分区,比如 /dev/sdb1
  • VG(Volume Group,卷组):把多个 PV 合并成一个大池子
  • LV(Logical Volume,逻辑卷):从 VG 池子里切出来的「虚拟分区」

打个比方:你有三块 100G 的硬盘(PV),把它们扔进一个 300G 的大池子(VG),然后从中切出 80G 给根目录、120G 给 home、16G 给 swap(三个 LV)。哪天根目录不够了,从池子里再划 20G 过来,在线扩容,服务不停。

3.1 创建 PV

$ sudo pvcreate /dev/sdb
  Physical volume "/dev/sdb" successfully created.

$ sudo pvs
  PV         VG  Fmt  Attr PSize   PFree
  /dev/sdb       lvm2 ---  100.00g 100.00g

3.2 创建 VG

$ sudo vgcreate vg_data /dev/sdb
  Volume group "vg_data" successfully created

$ sudo vgs
  VG      #PV #LV #SN Attr   VSize   VFree
  vg_data   1   0   0 wz--n- 100.00g 100.00g

3.3 创建 LV

从 vg_data 中切出 30G 作为数据存储卷:

$ sudo lvcreate -n lv_data -L 30G vg_data
  Logical volume "lv_data" created.

$ sudo lvs
  LV      VG      Attr       LSize  Pool Origin Data%  Meta%
  lv_data vg_data -wi-a----- 30.00g

3.4 格式化 + 挂载 LV

$ sudo mkfs.ext4 /dev/vg_data/lv_data
$ sudo mkdir -p /mnt/data
$ sudo mount /dev/vg_data/lv_data /mnt/data

$ df -h /mnt/data
Filesystem                    Size  Used Avail Use% Mounted on
/dev/mapper/vg_data-lv_data    30G   24K   28G   1% /mnt/data

永久挂载同理,加到 /etc/fstab

UUID=$(sudo blkid -s UUID -o value /dev/vg_data/lv_data)
echo "UUID=$UUID /mnt/data ext4 defaults 0 2" | sudo tee -a /etc/fstab

第四步:LVM 在线扩容 — 不停机加空间

这才是 LVM 的真正价值。假设 lv_data 的 30G 快满了,VG 池子里还有 70G 空闲。

4.1 扩展 LV

$ sudo lvextend -L +20G /dev/vg_data/lv_data
  Size of logical volume vg_data/lv_data changed from 30.00 GiB to 50.00 GiB.
  Logical volume vg_data/lv_data successfully resized.

4.2 扩展文件系统(ext4)

LV 扩容了,但文件系统还不知道。用 resize2fs(ext4 专用)让它识别新空间:

$ sudo resize2fs /dev/vg_data/lv_data
resize2fs 1.47.0 (5-Feb-2023)
Filesystem at /dev/vg_data/lv_data is mounted on /mnt/data; on-line resizing required
old_desc_blocks = 4, new_desc_blocks = 7
The filesystem on /dev/vg_data/lv_data is now 13107200 (4k) blocks long.

验证一下:

$ df -h /mnt/data
Filesystem                    Size  Used Avail Use% Mounted on
/dev/mapper/vg_data-lv_data    50G   24K   47G   1% /mnt/data

30G → 50G,在线完成,服务没中断。注意 XFS 文件系统用 xfs_growfs 而不是 resize2fs

4.3 如果要缩减空间呢?

ext4 支持缩容,但必须先卸载

$ sudo umount /mnt/data
$ sudo e2fsck -f /dev/vg_data/lv_data     # 先检查文件系统
$ sudo resize2fs /dev/vg_data/lv_data 20G  # 缩小文件系统到 20G
$ sudo lvreduce -L 20G /dev/vg_data/lv_data # 缩小 LV
$ sudo mount /dev/vg_data/lv_data /mnt/data

缩容有风险,生产环境建议直接加盘而不是缩容——磁盘很便宜,数据丢了哭都来不及。

第五步:实战场景 — VPS 根分区满了怎么办

这是最常遇到的问题。以 Ubuntu 24.04 VPS 为例,根分区 / 满了的排查和解决流程:

5.1 定位大文件

$ sudo du -h --max-depth=1 / 2>/dev/null | sort -hr | head -10
32G     /var
18G     /usr
4.2G    /home
2.1G    /opt
1.5G    /tmp
...

/var 占了 32G,进一步排查:

$ sudo du -h --max-depth=1 /var 2>/dev/null | sort -hr | head -5
28G     /var/log
2.5G    /var/lib
...

/var/log 是罪魁祸首——通常是被 Docker 或 Nginx 日志撑爆的。

5.2 清理日志(治标)

$ sudo journalctl --vacuum-size=500M        # systemd 日志限制 500M
$ sudo find /var/log -type f -name "*.log" -mtime +30 -delete  # 删 30 天前的日志
$ sudo docker system prune -a --volumes     # 清理 Docker 垃圾(谨慎!)

5.3 配置日志轮转(治本)

/etc/logrotate.d/ 下创建配置文件,让系统自动压缩和删除旧日志:

$ cat /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 640 root adm
}

5.4 如果还不够 — 扩容 VPS 磁盘

云厂商后台扩容磁盘后,还需要在系统里让分区识别新空间。如果用的是 LVM:

$ sudo pvresize /dev/sda3               # 让 PV 识别扩容后的磁盘
$ sudo lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv  # LV 吃掉所有剩余空间
$ sudo resize2fs /dev/ubuntu-vg/ubuntu-lv  # 扩展文件系统

如果不是 LVM(传统分区),就需要更麻烦的操作——这恰恰说明为什么建议生产环境用 LVM。

第六步:常用命令速查表

操作 命令
查看磁盘 lsblk, fdisk -l, df -h
分区 fdisk /dev/sdX
格式化 ext4 mkfs.ext4 /dev/sdX1
挂载 mount /dev/sdX1 /path
永久挂载 编辑 /etc/fstab,用 UUID
创建 PV pvcreate /dev/sdX
创建 VG vgcreate vg_name /dev/sdX
创建 LV lvcreate -n lv_name -L 10G vg_name
扩容 LV lvextend -L +5G /dev/vg/lv
扩容文件系统 resize2fs (ext4) / xfs_growfs (XFS)

常见问题(FAQ)

Q: 挂载时报错 “wrong fs type, bad option, bad superblock” 怎么办?

最常见的原因是该分区还没有格式化。先确认分区存在(lsblk),然后用 sudo mkfs.ext4 /dev/sdX1 格式化后再挂载。如果已经格式化过,检查文件系统是否损坏:sudo fsck /dev/sdX1

Q: LVM 和传统分区到底该怎么选?

推荐用 LVM,除非是极简场景(比如只有一个 20G 的小 VPS)。原因很简单:磁盘需求是动态增长的,你今天觉得 50G 够用,三个月后可能就需要 200G。LVM 让你在线扩容而不停机。Ubuntu Server 安装时默认就会使用 LVM,说明官方也推荐这个方案。唯一不推荐 LVM 的场景是嵌入式设备或极致性能要求的环境(多一层抽象有极轻微的性能开销)。

Q: /etc/fstab 写错了导致系统无法启动怎么办?

进入恢复模式(GRUB 菜单选 “Advanced options → recovery mode”),选 “Drop to root shell prompt”,然后:
1. mount -o remount,rw / 让根分区可写
2. nano /etc/fstab 修改或注释掉错误行
3. reboot 重启。
如果你用的是云 VPS(AWS/阿里云/腾讯云等),可以通过控制台的 VNC 或救援模式进入系统。所以改 fstab 前一定先跑 mount -a 验证。

总结

Linux 磁盘管理的核心就两条链路:

传统路线: fdisk 分区 → mkfs 格式化 → mount 挂载 → /etc/fstab 永久化

LVM 路线: pvcreate → vgcreate → lvcreate → mkfs → mount → lvextend + resize2fs 在线扩容

我个人现在所有 VPS 都用 LVM——曾经半夜两点被 /var 爆满搞过一次之后,就再也不想经历第二次了。花 10 分钟把 LVM 配好,换来的是随时在线扩容的安全感,值。

如果你刚接触 VPS,建议先在虚拟机里练一遍这些命令,搞坏了大不了快照回滚。等你用熟了,这些操作会像 lscd 一样自然。


推荐阅读:

Linux磁盘管理完全指南:分区、挂载与LVM扩容实战(2026)最先出现在编程·投资·科技

]]>
Linux crontab定时任务完全指南:从入门到生产环境实战(2026) https://www.devlearn.club/posts/627 Wed, 27 May 2026 01:02:52 +0000 https://www.devlearn.club/posts/627 什么是crontab?为什么每个运维都要…

Linux crontab定时任务完全指南:从入门到生产环境实战(2026)最先出现在编程·投资·科技

]]>
什么是crontab?为什么每个运维都要掌握它?

在Linux服务器运维中,定时任务是最基础也最频繁的需求之一。备份数据库、清理日志、发送监控告警、更新SSL证书——这些重复性的工作如果全靠人工手动执行,不仅效率低下,还容易遗漏。

crontab(cron table)是Linux系统中最经典的定时任务管理工具。它能让系统在指定的时间自动执行预设的命令或脚本,是运维工程师必备的核心技能。

本教程将从crontab的基本语法开始,逐步深入到生产环境中的实战场景和最佳实践,无论你是刚接触Linux的新手,还是有几年经验的运维老手,都能从中获得实实在在的帮助。

一、crontab基础:安装与常用命令

大多数Linux发行版默认已安装cron。先确认一下你的系统状态:

$ systemctl status cron
● cron.service - Regular background program processing daemon
     Loaded: loaded (/lib/systemd/system/cron.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-05-25 08:00:01 CST
   Main PID: 1234 (cron)
     Tasks: 1 (limit: 2327)
    Memory: 2.1M
        CPU: 85ms
     CGroup: /system.slice/cron.service
            └── 1234 /usr/sbin/cron -f

如果没安装,一条命令搞定:

$ sudo apt update && sudo apt install cron -y     # Debian/Ubuntu
$ sudo yum install cronie -y                       # CentOS/RHEL

crontab的常用命令只有5个,背下来就行:

$ crontab -e            # 编辑当前用户的定时任务(最常用)
$ crontab -l            # 列出当前用户的定时任务
$ crontab -r            # 删除当前用户的所有定时任务(慎用!)
$ crontab -u username   # 管理指定用户的定时任务(需要root)
$ crontab -i            # 删除前先确认(安全开关)

二、cron表达式:5个星号搞定一切

crontab的语法看起来简单,但写错一个星号就可能让任务不按预期执行。来,记住这个口诀:

┌───────── 分钟 (0 - 59)
│ ┌───────── 小时 (0 - 23)
│ │ ┌───────── 日期 (1 - 31)
│ │ │ ┌───────── 月份 (1 - 12)
│ │ │ │ ┌───────── 星期 (0 - 7, 0和7都表示周日)
│ │ │ │ │
* * * * * <要执行的命令>

几个特殊字符帮你写出更灵活的表达式:

  • * — 每(每个分钟/小时/天…)
  • , — 多个值(如 1,15,30 表示第1、15、30分钟)
  • - — 范围(如 9-17 表示9点到17点)
  • / — 步长(如 */5 表示每5分钟)

三、10个最常用的cron表达式示例

直接上实战,以下场景你大概率都会遇到:

# 每分钟执行一次
* * * * * /usr/bin/php /var/www/cron/every_minute.php

# 每5分钟执行一次
*/5 * * * * /usr/bin/python3 /opt/scripts/check_health.py

# 每天凌晨2:30执行(日志清理的经典时间)
30 2 * * * /usr/local/bin/clean_logs.sh

# 每天上午8点和下午6点各执行一次
0 8,18 * * * /opt/scripts/send_report.sh

# 每周一凌晨3:00执行
0 3 * * 1 /usr/local/bin/weekly_backup.sh

# 每月1号凌晨4:00执行
0 4 1 * * /usr/local/bin/monthly_report.sh

# 每季度执行(1、4、7、10月的凌晨5点)
0 5 1 1,4,7,10 * /usr/local/bin/quarterly_cleanup.sh

# 工作日(周一到周五)每2小时执行一次
0 */2 * * 1-5 /opt/scripts/workday_task.sh

# 每半小时执行一次(适合频繁监控脚本)
*/30 * * * * /opt/scripts/monitor_memory.sh

# 每小时的第15分钟执行
15 * * * * /usr/local/bin/hourly_task.sh

四、实战场景1:Nginx日志每日切割与清理

这是运维中最常见的需求之一。Nginx访问日志如果不做切割,单个文件很快就能撑满磁盘。

先写一个日志切割脚本 /usr/local/bin/rotate_nginx_logs.sh

#!/bin/bash
# Nginx日志每日切割脚本
LOG_DIR="/var/log/nginx"
YESTERDAY=$(date -d "yesterday" +"%Y%m%d")

# 切割访问日志
mv $LOG_DIR/access.log $LOG_DIR/access_$YESTERDAY.log
mv $LOG_DIR/error.log $LOG_DIR/error_$YESTERDAY.log

# 向Nginx发送USR1信号,让它重新打开日志文件
kill -USR1 $(cat /var/run/nginx.pid)

# 保留最近30天的日志,删除更早的
find $LOG_DIR -name "*.log" -mtime +30 -delete

echo "[$(date)] Nginx logs rotated, kept last 30 days"
$ chmod +x /usr/local/bin/rotate_nginx_logs.sh

然后添加到crontab:

$ crontab -e
# 加入这一行,每天凌晨0点执行日志切割
0 0 * * * /usr/local/bin/rotate_nginx_logs.sh >> /var/log/rotate_cron.log 2>&1

五、实战场景2:MySQL数据库自动备份

数据是命根子,自动备份必须安排上:

#!/bin/bash
# MySQL每日备份脚本
BACKUP_DIR="/data/backups/mysql"
DB_USER="backup_user"
DB_PASS="your_secure_password"
DB_NAME="wordpress"
DATE=$(date +"%Y%m%d_%H%M")
RETENTION_DAYS=7

# 创建备份目录
mkdir -p $BACKUP_DIR

# 执行备份
mysqldump -u$DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_DIR/${DB_NAME}_$DATE.sql.gz

# 保留最近7天,删除旧备份
find $BACKUP_DIR -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete

echo "[$(date)] Backup completed: ${DB_NAME}_$DATE.sql.gz ($(du -sh $BACKUP_DIR/${DB_NAME}_$DATE.sql.gz | cut -f1))"

然后添加定时任务(每天凌晨3点备份,避开业务高峰期):

0 3 * * * /usr/local/bin/backup_mysql.sh >> /var/log/backup.log 2>&1

六、实战场景3:系统资源监控与告警

磁盘快满了还等用户来报?自动监控才是正解:

#!/bin/bash
# 磁盘使用率监控告警脚本
THRESHOLD=85
EMAIL="admin@example.com"

df -H | grep -vE '^Filesystem|tmpfs|overlay' | awk '{print $5 " " $6}' | while read output;
do
  usage=$(echo $output | awk '{print $1}' | cut -d'%' -f1)
  partition=$(echo $output | awk '{print $2}')
  if [ $usage -ge $THRESHOLD ]; then
    echo "⚠ 磁盘告警: $partition 使用率已达 $usage%" | \
    mail -s "[CRITICAL] 磁盘空间告警 - $(hostname)" $EMAIL
  fi
done
# 每10分钟检查一次
*/10 * * * * /usr/local/bin/disk_monitor.sh

七、生产环境最佳实践

1. 始终使用绝对路径

cron的环境变量和终端不同,PATH非常有限。命令和脚本路径一律写绝对路径:

# ❌ 错误方式——cron可能找不到python3
*/5 * * * * python3 /opt/scripts/check.py

# ✅ 正确方式——用绝对路径
*/5 * * * * /usr/bin/python3 /opt/scripts/check.py

2. 输出重定向与日志

cron的默认输出会发邮件给用户,大部分服务器没配邮件,你的日志就丢了。一定要重定向:

# 标准输出和错误都记录到日志文件
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# 或丢弃所有输出(确定脚本稳定后)
0 2 * * * /usr/local/bin/backup.sh > /dev/null 2>&1

3. 加锁防止重复执行

对于执行时间可能超过间隔的任务(比如大数据量的数据库备份),需要加锁:

#!/bin/bash
# 使用flock确保同一时间只有一个实例在运行
LOCKFILE="/tmp/backup_mysql.lock"

exec 200>$LOCKFILE
flock -n 200 || exit 1

# 这里是实际的任务内容
mysqldump -u$DB_USER -p$DB_PASS $DB_NAME | gzip > /data/backup/db_$(date +%Y%m%d).sql.gz

flock -u 200

4. 设置环境变量

在crontab文件顶部设置环境变量:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=admin@example.com

0 2 * * * /usr/local/bin/backup.sh

5. 使用crontab.guru验证表达式

写没把握的表达式时,去 crontab.guru 验证一下,把5个星号填进去,它告诉你实际什么时间执行。这个习惯能救你很多次。本神亲测有效。

八、故障排查:cron任务没执行怎么办?

crontab没按预期执行,90%是以下原因:

  • 路径问题 — cron的PATH远比你终端的短。始终用绝对路径。
  • 权限问题 — 脚本没加执行权限(chmod +x
  • 语法错误 — 用 crontab -l 检查实际写入的内容
  • cron服务没运行 — 检查 systemctl status cron
  • 系统时间不对 — 用 date 确认时区,timedatectl set-timezone Asia/Shanghai 修正

排查命令一条龙:

$ grep CRON /var/log/syslog | tail -20     # Ubuntu/Debian 查看cron日志
$ journalctl -u cron --since "1 hour ago"    # systemd 查看cron日志
$ crontab -l                                 # 确认任务已写入
$ ls -la /var/spool/cron/crontabs/           # crontab文件实际位置

九、进阶:anacron——解决服务器关机错过任务的问题

如果你的服务器不是7×24小时运行(比如笔记本或定时开关机的开发机),cron在关机期间的任务会直接跳过。这时候需要 anacron——它会在系统启动后补执行错过的任务。

$ sudo apt install anacron -y

anacron的任务配置在 /etc/anacrontab

# 格式:周期(天) 延迟(分钟) 任务名 命令
1       5       daily_backup    /usr/local/bin/backup.sh
7       10      weekly_clean    /usr/local/bin/weekly_cleanup.sh
30      15      monthly_report  /usr/local/bin/monthly_report.sh

第一列的天数是什么意思?就是”任务至少每N天执行一次”。如果系统关机了3天,开机后5分钟(第二列设置的延迟)就会执行daily_backup。

常见问题(FAQ)

Q: crontab -e 用什么编辑器?怎么改成vim?

默认是nano,想换成vim执行:select-editor (Ubuntu)或 export EDITOR=vim (加到 ~/.bashrc 永久生效)。

Q: crontab 支持秒级定时任务吗?

不支持。cron的最小粒度是分钟。如果需要秒级任务,用 while sleep 10; do ... done 包装成后台服务,或用 systemd timer 实现。

Q: 修改了crontab需要重启服务吗?

不需要。cron会实时监控crontab文件的变化,crontab -e 保存后立即生效。但如果修改了系统的 /etc/crontab,则需要 systemctl restart cron

— 本文发布于2026年5月27日,命令均在Ubuntu 24.04 LTS / Debian 12上验证通过。如有更新,欢迎收藏订阅。

Linux crontab定时任务完全指南:从入门到生产环境实战(2026)最先出现在编程·投资·科技

]]>
systemctl服务管理完全指南:从systemd入门到生产环境服务编排(2026) https://www.devlearn.club/posts/598 Mon, 25 May 2026 01:02:31 +0000 https://www.devlearn.club/posts/598 前言 systemd 是当今绝大多数 L…

systemctl服务管理完全指南:从systemd入门到生产环境服务编排(2026)最先出现在编程·投资·科技

]]>
前言

systemd 是当今绝大多数 Linux 发行版的初始化系统和服务管理器。从 Ubuntu 16.04+、Debian 8+、CentOS 7+ 到 RHEL 8+,systemd 早已取代传统的 SysV init 成为标配。而 systemctl 就是控制 systemd 的核心命令——不懂 systemctl,就等于不会管理 Linux 服务。

本教程将从最基础的 service 启动/停止/重启讲起,逐步深入到服务依赖配置、多服务编排、故障排查和性能优化,帮你一次性吃透 systemctl。

一、基础篇:服务的生命周期管理

1. 查看服务状态

最常用的操作就是查看某个服务是否在运行:

$ systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2026-05-25 08:30:15 UTC; 2h 15min ago
       Docs: man:nginx(8)
   Main PID: 12345 (nginx)
      Tasks: 2 (limit: 2345)
     Memory: 12.3M
        CPU: 0.245s
     CGroup: /system.slice/nginx.service
             ├─12345 nginx: master process /usr/sbin/nginx -g daemon off;
             └─12346 nginx: worker process

输出信息非常丰富:

  • Loaded:服务是否已加载,以及是否设置为开机自启(enabled/disabled)
  • Active:当前运行状态,后面带运行时长
  • Main PID:主进程ID
  • Memory/CPU:资源占用情况

2. 启动、停止与重启服务

# 启动服务
$ sudo systemctl start nginx

# 停止服务
$ sudo systemctl stop nginx

# 重启服务
$ sudo systemctl restart nginx

# 重新加载配置(不中断服务)
$ sudo systemctl reload nginx

restart 会先 stop 再 start,有短暂中断。而 reload 只让服务重新读取配置文件,不会中断正在处理的请求——生产环境优先用 reload

3. 设置开机自启

# 启用开机自启
$ sudo systemctl enable nginx
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.

# 禁用开机自启
$ sudo systemctl disable nginx
Removed "/etc/systemd/system/multi-user.target.wants/nginx.service".

# 查看是否已启用
$ systemctl is-enabled nginx
enabled

# 启用并立即启动(一步到位)
$ sudo systemctl enable --now nginx

enable --now 是实战中最常用的组合拳——设置开机自启的同时立即启动服务,省得敲两遍命令。

二、进阶篇:服务状态查询

4. 列出所有服务

# 列出所有正在运行的服务
$ systemctl list-units --type=service --state=running

# 列出所有服务(包括未运行的)
$ systemctl list-units --type=service --all

# 列出所有已启用的服务
$ systemctl list-unit-files --type=service --state=enabled

# 列出所有启动失败的服务(排查问题专用)
$ systemctl --failed
  UNIT                    LOAD   ACTIVE SUB    DESCRIPTION
● nginx.service          loaded failed failed nginx - high performance web server

systemctl --failed 是服务器日常巡检的黄金命令,一眼找出所有挂掉的服务。

5. 检查服务是否存活

# 检查单个服务是否正在运行(返回码可用于脚本)
$ systemctl is-active nginx
active

# 批量检查
$ for svc in nginx mysql redis; do
    echo "$svc: $(systemctl is-active $svc)"
done
nginx: active
mysql: active
redis: inactive

6. 查看服务依赖关系

# 查看某服务依赖了哪些其他单元
$ systemctl list-dependencies nginx
nginx.service
● ├─system.slice
● ├─network.target
● └─basic.target

# 查看哪些服务依赖该服务
$ systemctl list-dependencies --reverse nginx
nginx.service
● └─multi-user.target

依赖关系在排查启动顺序问题时至关重要——比如你的应用需要网络后才启动,而网络服务还没就绪,那应用自然起不来。

三、高级篇:编写自己的服务单元

7. 编写一个 systemd service 文件

假设你有一个 Python Web 应用 /opt/myapp/app.py,想把它注册为系统服务:

$ sudo vim /etc/systemd/system/myapp.service
[Unit]
Description=My Python Web Application
After=network.target
Wants=redis.service

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=always
RestartSec=5
Environment="PYTHONPATH=/opt/myapp"
Environment="LOG_LEVEL=info"

[Install]
WantedBy=multi-user.target

关键字段解释:

  • After:声明本服务在网络就绪后再启动(不是依赖,只是顺序)
  • Wants:弱依赖——Redis 启动失败不会阻止本服务启动
  • Type=simple:最常见的类型,主进程直接运行在前台
  • Restart=always:只要进程退出就自动重启(生产环境必配)
  • RestartSec=5:重启前等待5秒,避免频繁重启打满CPU

8. 加载并启动自定义服务

# 重新加载 systemd 配置(每次修改 .service 文件后必须执行)
$ sudo systemctl daemon-reload

# 启动自定义服务
$ sudo systemctl start myapp

# 查看状态
$ systemctl status myapp

# 设置为开机自启
$ sudo systemctl enable myapp

重要: 每次修改 .service 文件,都必须运行 systemctl daemon-reload,否则 systemd 不会感知变更。

9. 几种常见的 Service Type

Type 适用场景 说明
simple 普通应用、Web服务 ExecStart 指定的进程就是主进程,一直运行在前台
forking 传统 daemon 程序(如 Nginx) 进程 fork 后父进程退出,子进程继续运行;需要配合 PIDFile
oneshot 一次性任务、初始化脚本 执行一次就退出,可配合 RemainAfterExit=yes 标记执行成功状态
notify 支持 sd_notify 的现代应用 服务启动完成后主动通知 systemd,更精确的健康检测

以 Nginx 为例,它的 service 文件使用了 Type=forking

$ cat /lib/systemd/system/nginx.service | head -15
[Unit]
Description=A high performance web server and a reverse proxy server

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=/usr/sbin/nginx -g 'daemon on; master_process on;' -s quit

四、生产环境实战:服务编排

10. 多服务依赖启动

假设你有一个 Web 应用依赖 MySQL 和 Redis:

[Unit]
Description=My Web App
Requires=mysql.service redis.service
After=network.target mysql.service redis.service

[Service]
Type=simple
ExecStart=/usr/bin/java -jar /opt/app/app.jar
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

RequiresAfter 的区别:

  • Requires:强依赖——如果 MySQL 或 Redis 启动失败,本服务也不会启动
  • After:只控制启动顺序,不决定是否启动

两者通常配合使用:Requires 保证依赖存在,After 保证顺序正确。

11. 使用 systemctl 管理定时任务(systemd-timer)

systemd 自带 timer 机制,可以替代 crontab:

# 创建定时器单元
$ sudo vim /etc/systemd/system/backup.service
[Unit]
Description=Daily Database Backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

$ sudo vim /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 3am

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

# 启用定时器
$ sudo systemctl enable --now backup.timer

# 查看所有定时器
$ systemctl list-timers
NEXT                        LEFT     LAST                        PASSED    UNIT         ACTIVATES
Mon 2026-05-26 03:00:00 UTC 17h left Sun 2026-05-25 03:00:00 UTC 7h ago    backup.timer backup.service

12. 日志查看与排错

# 查看服务的完整日志
$ journalctl -u nginx

# 只看最近的20条
$ journalctl -u nginx -n 20

# 实时追踪日志(类似 tail -f)
$ journalctl -u nginx -f

# 查看指定时间段的日志
$ journalctl -u nginx --since "2026-05-25 08:00:00" --until "2026-05-25 09:00:00"

# 只看错误级别以上的日志
$ journalctl -u nginx -p err

# 查看内核日志
$ journalctl -k

13. 屏蔽与掩码服务

# 禁用服务(disable 只是取消开机自启,还可以手动启动)
$ sudo systemctl disable apache2

# 彻底屏蔽服务(连手动启动都不允许)
$ sudo systemctl mask apache2
Created symlink /etc/systemd/system/apache2.service → /dev/null.

# 取消屏蔽
$ sudo systemctl unmask apache2

mask 在安全加固时非常有用——如果某服务存在已知漏洞且暂不修复,直接 mask 掉,彻底杜绝意外启动。

五、性能与调试技巧

14. 分析服务启动耗时

# 查看系统启动总耗时
$ systemd-analyze
Startup finished in 3.245s (kernel) + 12.876s (userspace) = 16.121s
graphical.target reached after 12.876s in userspace

# 查看每个服务启动耗时排行榜
$ systemd-analyze blame
 5.234s mysql.service
 3.876s networking.service
 2.145s systemd-udevd.service
 1.234s nginx.service
 0.876s redis.service
 0.345s ssh.service

# 生成启动时间 SVG 火焰图
$ systemd-analyze plot > boot.svg

服务器启动慢?跑一遍 systemd-analyze blame,一眼锁定最慢的服务,针对性优化。

15. 查看服务的资源占用

# 以类似 top 的方式查看所有服务资源占用
$ systemd-cgtop
Control Group                                Tasks   %CPU   Memory  Input/s Output/s
/system.slice/mysql.service                  12      2.3%   1.2G    0B      0B
/system.slice/nginx.service                  6       0.8%   45.3M   0B      0B
/system.slice/redis.service                  4       0.1%   12.1M   0B      0B

六、排查指南

常见问题速查

  • 服务启动失败但没报错?journalctl -xe 查看最新系统日志
  • 修改了 .service 文件没生效? → 先执行 systemctl daemon-reload
  • 服务起起停停? → 检查 Restart= 设置,可能是程序本身 crash 了
  • 开机自启没生效? → 确认 systemctl is-enabled 返回 enabled,并且 [Install] 段有 WantedBy=multi-user.target
  • 端口被占用无法启动?ss -tlnp | grep :端口号 找谁在用

常见问题(FAQ)

Q1: systemctl 和 service 命令有什么区别?

service nginx start 是传统 SysV init 时代的命令,兼容性包装器。在 systemd 系统上,它最终还是会调用 systemctl。而 systemctl start nginx 是直接操作 systemd 的原生命令。建议统一使用 systemctl,输出更详细、功能更完整。

Q2: systemctl enable 和 systemctl start 必须一起用吗?

不一定。enable 只设置开机自启(创建符号链接),start 是立即启动。你可以只 startenable(本次开机运行,重启后不自动启动),也可以只 enablestart(下次重启后自动启动)。但实战中常用 systemctl enable --now 一步到位解决两个需求。

Q3: 修改了 .service 文件后为什么 systemctl restart 没有用?

因为 systemd 在启动时会缓存 service 文件的副本。修改文件后必须执行 systemctl daemon-reload 让 systemd 重新加载配置,然后再 systemctl restart 服务名。这是新手最常见的错误,没有之一。

systemctl服务管理完全指南:从systemd入门到生产环境服务编排(2026)最先出现在编程·投资·科技

]]>
Let’s Encrypt SSL证书自动续期配置:Certbot安装到全自动化维护(2026) https://www.devlearn.club/posts/580 Fri, 22 May 2026 01:02:02 +0000 https://www.devlearn.club/posts/580 前言:为什么需要自动续期SSL证书 Le…

Let’s Encrypt SSL证书自动续期配置:Certbot安装到全自动化维护(2026)最先出现在编程·投资·科技

]]>
前言:为什么需要自动续期SSL证书

Let’s Encrypt 是目前最受欢迎的免费 SSL 证书颁发机构,但其证书有效期仅为 90 天。这意味着每三个月你都必须手动续期一次——如果你的站点配置了 Nginx、Apache 或其他 Web 服务器,忘记续期会导致浏览器显示”不安全”警告,直接影响用户体验和 SEO 排名。

好消息是,Let’s Encrypt 官方推荐的 Certbot 客户端内置了自动续期机制。本文将从零开始,带你完成从 Certbot 安装、首次证书签发,到自动化续期、续期后服务重载的完整配置。

第一步:安装 Certbot

Certbot 是 Let’s Encrypt 官方推荐的 ACME 客户端。根据你的操作系统选择对应的安装方式:

Ubuntu / Debian

$ sudo apt update
$ sudo apt install certbot python3-certbot-nginx    # 如果使用 Nginx
$ sudo apt install certbot python3-certbot-apache   # 如果使用 Apache

安装完成后验证版本:

$ certbot --version
certbot 2.11.0

CentOS / Rocky Linux / AlmaLinux

$ sudo dnf install epel-release
$ sudo dnf install certbot python3-certbot-nginx

第二步:首次签发 SSL 证书

Certbot 提供多种插件来签发证书。最常用的两种方式:

方式 A:使用 Nginx 插件(推荐,全自动)

如果 Nginx 配置文件已经指向你的域名,使用 Nginx 插件可以一键完成:

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

Certbot 会自动:

  • 验证域名所有权(通过 HTTP-01 挑战)
  • 修改 Nginx 配置,添加 SSL 相关指令
  • 配置 301 HTTP → HTTPS 重定向(可选)

实际输出示例:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for example.com and www.example.com
Performing the following challenges:
http-01 challenge for example.com
http-01 challenge for www.example.com
Waiting for verification...
Cleaning up challenges
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/example.com/privkey.pem
Deploying certificate
Successfully deployed certificate for example.com
Successfully deployed certificate for www.example.com
Congratulations! You have successfully enabled HTTPS!

方式 B:使用 Webroot 插件(手动配置)

如果不想让 Certbot 修改 Nginx 配置,或者使用自定义 Web 服务器:

$ sudo certbot certonly --webroot -w /var/www/example.com -d example.com -d www.example.com

这种方式仅在 Nginx 中手动添加一个 location 块指向验证目录(通常不需要手动操作,Certbot 会自动在 /.well-known/acme-challenge/ 下放置验证文件)。

第三步:验证证书文件

签发成功后,证书文件存储在 /etc/letsencrypt/live/your-domain/ 目录下:

$ sudo ls -la /etc/letsencrypt/live/example.com/
lrwxrwxrwx 1 root root  39 May 22 09:00 cert.pem -> ../../archive/example.com/cert1.pem
lrwxrwxrwx 1 root root  40 May 22 09:00 chain.pem -> ../../archive/example.com/chain1.pem
lrwxrwxrwx 1 root root  44 May 22 09:00 fullchain.pem -> ../../archive/example.com/fullchain1.pem
lrwxrwxrwx 1 root root  42 May 22 09:00 privkey.pem -> ../../archive/example.com/privkey1.pem

各文件用途:

  • cert.pem:服务端证书(仅证书本身)
  • chain.pem:中间证书链
  • fullchain.pem:服务端证书 + 中间证书链(Nginx 常用此文件)
  • privkey.pem:私钥文件(⚠ 绝对不要公开)

Nginx 配置中的典型引用方式:

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

第四步:测试自动续期

Certbot 安装时会自动添加一个 systemd timercron 任务,每天检查两次证书是否即将到期(到期前 30 天内会触发续期)。

首先,用 dry-run 模式测试续期流程是否正常:

$ sudo certbot renew --dry-run

Saving debug log to /var/log/letsencrypt/letsencrypt.log

Processing /etc/letsencrypt/renewal/example.com.conf
Account registered.
Simulating renewal of an existing certificate for example.com and www.example.com
Waiting for verification...
Cleaning up challenges
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)

如果输出 all simulated renewals succeeded,说明续期机制一切正常。

查看系统自带的续期定时器:

$ sudo systemctl status certbot.timer
● certbot.timer - Run certbot twice daily
     Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; vendor preset: enabled)
     Active: active (waiting) since Thu 2026-05-21 09:00:00 UTC; 1 day ago
    Trigger: Sat 2026-05-23 03:49:39 UTC; 18h left
   Triggers: ● certbot.service

如果系统中没有自动创建 systemd timer(例如某些旧版本),可以手动添加 cron 任务:

$ sudo crontab -e
# 每天凌晨 3:00 检查续期,如果成功则重载 Nginx
0 3 * * * /usr/bin/certbot renew --quiet && systemctl reload nginx

第五步:配置续期后服务重载

证书更新后,Web 服务器需要重新加载才能使用新证书。Certbot 的 renew 钩子可以自动完成:

方式 1:通过 deploy hook(推荐)

/etc/letsencrypt/renewal-hooks/deploy/ 目录下创建一个脚本:

$ sudo tee /etc/letsencrypt/renewal-hooks/deploy/restart-nginx.sh << 'EOF'
#!/bin/bash
systemctl reload nginx || true
EOF

$ sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/restart-nginx.sh

这个脚本只会在证书 成功更新后 执行,不会在 dry-run 或续期失败时触发。

方式 2:使用 –post-hook 参数

也可以将重载命令直接写入续期命令:

$ sudo certbot renew --post-hook "systemctl reload nginx"

但 deploy hook 更加规范,因为它是 Certbot 原生支持的钩子系统,且按字母顺序执行所有 deploy 目录下的脚本。

第六步:验证自动续期是否生效

手动强制过期检查来验证整个自动续期链条:

$ sudo certbot renew --force-renewal --deploy-hook "systemctl reload nginx"

这条命令会强制续期所有证书(即使距到期还有 90 天),续期后立即重载 Nginx。查看日志确认:

$ sudo tail -20 /var/log/letsencrypt/letsencrypt.log
2026-05-22 09:15:42,123:DEBUG:certbot.renewal:Certificate renewed
2026-05-22 09:15:42,125:DEBUG:certbot.renewal:Running deploy hooks
2026-05-22 09:15:42,130:INFO:certbot.renewal:Deploy hook 'restart-nginx.sh' ran successfully

最后,检查证书到期时间:

$ sudo openssl x509 -enddate -noout -in /etc/letsencrypt/live/example.com/cert.pem
notAfter=Aug 20 09:15:42 2026 GMT

到期时间向后推移了 90 天,证明续期成功。

第七步:多域名与泛域名证书

多域名证书

一张证书可以包含多个域名,签发时用 -d 参数指定:

$ sudo certbot --nginx -d example.com -d www.example.com -d blog.example.com -d api.example.com

泛域名证书(Wildcard Certificate)

如果要保护 *.example.com 下的所有子域名,需要使用 DNS-01 挑战(因为 HTTP-01 无法验证通配符域名):

$ sudo certbot certonly --manual --preferred-challenges dns -d *.example.com -d example.com

Certbot 会提示你在 DNS 管理面板中添加一条 TXT 记录:

_acme-challenge.example.com  TXT  "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

添加完成后等待 1-2 分钟让 DNS 传播,然后回车确认。

泛域名证书的续期同样需要 DNS 验证——考虑使用支持 DNS API 插件(如 Cloudflare、Aliyun DNS、DNSPod)实现全自动续期:

$ sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials ~/.cloudflare/credentials.ini -d *.example.com -d example.com

常见问题 FAQ

Q1: Let’s Encrypt 续期失败,提示 “No valid IP addresses” 怎么办?

A: 最常见原因是 DNS 解析问题或防火墙阻止了 80 端口(HTTP-01 验证需要)。检查步骤:

  1. 确认域名 A 记录正确指向服务器 IP:dig +short example.com
  2. 确认服务器 80 端口可访问:curl -I http://example.com/.well-known/acme-challenge/test,如果返回 404 而不是连接失败,说明端口通
  3. 检查防火墙是否放行 80 端口:sudo ufw status 确认 80/tcp 状态为 ALLOW
  4. 如果使用 CDN(Cloudflare 等),将域名切换为 DNS Only(灰色云朵)模式,或使用 DNS-01 挑战

Q2: Certbot 续期时提示 “The certificate will expire soon”,但自动续期没触发?

A: 检查 systemd timer 是否正常运行:

sudo systemctl status certbot.timer
sudo systemctl list-timers | grep certbot

如果 timer 停用,手动启用:sudo systemctl enable --now certbot.timer。如果 timer 正常但依然不续期,运行 sudo certbot renew --dry-run 查看具体错误。

如果使用的是 cron 而非 systemd,确保 crontab 中指定了绝对路径:/usr/bin/certbot,因为 cron 的环境变量有限。

Q3: 如何迁移 Let’s Encrypt 证书到新服务器?

A: 最简单的做法是在新服务器上重新签发证书(因为 Let’s Encrypt 有速率限制,每周最多 50 张证书,个人站点不用担心)。如果必须迁移:

  1. 在旧服务器上打包整个 /etc/letsencrypt 目录:sudo tar czf letsencrypt-backup.tar.gz /etc/letsencrypt
  2. 将文件复制到新服务器相同路径
  3. 在新服务器上重载 Nginx:sudo systemctl reload nginx
  4. 在新服务器上运行 sudo certbot renew --dry-run 确认自动续期正常

注意:迁移后记得在新服务器上重新安装 Certbot 并确认 systemd timer 已启用。

总结

Let’s Encrypt SSL 证书的自动续期配置并不复杂:安装 Certbot → 签发证书 → 验证自动续期 → 配置 deploy hook。一旦配置完成,你基本上可以忘记证书这回事——Certbot 的 systemd timer 会每天检查两次,在到期前 30 天自动续期,deploy hook 会重载 Web 服务器。

如果你的站点已经配置了 Let’s Encrypt 但还没验证自动续期是否正常工作,建议马上运行 sudo certbot renew --dry-run 确认。还有什么比 HTTPS 证书突然过期更让人崩溃的呢?

Let’s Encrypt SSL证书自动续期配置:Certbot安装到全自动化维护(2026)最先出现在编程·投资·科技

]]>
Linux 发行版怎么选?Ubuntu vs Debian vs CentOS 实战对比 https://www.devlearn.club/posts/558 Thu, 21 May 2026 07:37:32 +0000 https://www.devlearn.club/posts/558 刚接触 Linux 的人最常问的:「Ub…

Linux 发行版怎么选?Ubuntu vs Debian vs CentOS 实战对比最先出现在编程·投资·科技

]]>
Linux 发行版怎么选?Ubuntu vs Debian vs CentOS 实战对比

刚接触 Linux 的人最常问的:「Ubuntu 和 CentOS 到底有什么区别?我该装哪个?」这问题就像问「丰田和大众选哪个」——先看你拿来干什么。

服务器端格局

Ubuntu 一家独大,用户友好 + 社区庞大。Debian 稳如老狗,机房首选。CentOS/RHEL 是企业级标配。

我该用哪个?

Ubuntu(新手推荐)

文档最多,遇到问题 Google 一下基本都有答案。apt 比 yum 顺手。每两年一个 LTS 版本,支持 5 年。

Debian

极其稳定。我的个人观点——Debian 才是真正的「生产环境」系统,Ubuntu 只是新手友好版。AWS 和 Google Cloud 的默认镜像很多都是 Debian。软件包比 Ubuntu 略旧。

CentOS Stream / RHEL

红帽生态,企业级支持。CentOS 8 已经停更了(2021年),现在是 CentOS Stream 滚动发布。

其实选哪个差别不大

一旦你搞懂了 Linux 的基本概念,换发行版也就是换个包管理器的事。我更建议新手先装 Ubuntu 用上半年,把日常操作练熟了再考虑换。

快速上手指南

# Ubuntu/Debian
sudo apt update && sudo apt upgrade -y
sudo apt install curl wget vim git ufw -y
sudo ufw enable

# CentOS/RHEL
sudo dnf update -y
sudo dnf install curl wget vim git -y
sudo systemctl enable firewalld --now

总结

新手无脑选 Ubuntu,要稳选 Debian,公司用跟着同事走。别在这个问题上纠结超过 10 分钟——真正该花时间的是学 Linux 本身,不是选版本。

Linux 发行版怎么选?Ubuntu vs Debian vs CentOS 实战对比最先出现在编程·投资·科技

]]>
Linux 常用命令大全:文件操作、文本处理、网络与系统管理速查手册 https://www.devlearn.club/posts/549 Thu, 21 May 2026 05:49:45 +0000 https://www.devlearn.club/posts/549 为什么需要掌握 Linux 命令? 90…

Linux 常用命令大全:文件操作、文本处理、网络与系统管理速查手册最先出现在编程·投资·科技

]]>
为什么需要掌握 Linux 命令?

90% 的服务器运行在 Linux 上。无论是管理 VPS、部署应用还是排查故障,命令行都是你最高效的武器。本文整理了日常高频使用频率最高的 Linux 命令,按功能分类。

文件操作

ls -la              # 列出文件(含隐藏文件)
cd /path            # 切换目录
pwd                 # 显示当前路径
cp file1 file2      # 复制文件
cp -r dir1 dir2     # 复制目录
mv file1 file2      # 移动/重命名
rm file             # 删除文件
rm -rf dir          # 强制删除目录
mkdir -p a/b/c      # 创建多级目录
touch file          # 创建空文件或更新修改时间
cat file            # 查看文件内容
less file           # 分页查看(按 q 退出)
head -n 20 file     # 查看前 20 行
tail -n 20 file     # 查看后 20 行
tail -f file        # 实时跟踪文件变化(日志常用)

文件权限

chmod 755 file      # 设置权限(rwxr-xr-x)
chmod +x script.sh  # 添加执行权限
chown user:group file # 修改文件所有者
umask               # 查看默认权限掩码

文本处理

grep 'pattern' file        # 搜索文本
grep -r 'pattern' dir/     # 递归搜索
grep -i 'pattern' file     # 忽略大小写
grep -v 'pattern' file     # 反向匹配

sed 's/old/new/g' file     # 替换文本
sed -i 's/old/new/g' file  # 直接修改文件

awk '{print $1}' file      # 打印第一列
awk -F: '{print $1,$3}' /etc/passwd  # 指定分隔符

wc -l file                 # 统计行数
sort file                  # 排序
uniq                       # 去重
cut -d: -f1 /etc/passwd   # 按列切割

进程管理

ps aux                    # 查看所有进程
ps aux | grep nginx       # 搜索特定进程
top                       # 实时进程监控(按 q 退出)
htop                      # 增强版 top(需安装)
kill PID                  # 终止进程
kill -9 PID               # 强制终止
killall nginx             # 终止所有同名进程
nohup command &           # 后台运行(退出终端不终止)

网络工具

ping google.com           # 测试网络连通
curl -I https://example.com  # 查看 HTTP 响应头
curl -s https://api.example.com  # 发送 API 请求
wget https://example.com/file.zip  # 下载文件
ss -tlnp                 # 查看监听端口
netstat -tlnp            # 同上(旧工具)
dig example.com          # DNS 查询
nslookup example.com     # DNS 查询
traceroute google.com    # 路由追踪
telnet host port         # 测试端口连通

磁盘和系统

df -h                   # 查看磁盘使用情况
du -sh *                # 查看当前目录各文件大小
free -h                 # 查看内存使用
uname -a                # 查看系统内核信息
uptime                  # 查看系统运行时间
lscpu                   # 查看 CPU 信息
dmesg | tail            # 查看内核日志
journalctl -u sshd      # 查看 systemd 服务日志

压缩与归档

tar czf archive.tar.gz dir/    # 压缩目录
tar xzf archive.tar.gz         # 解压
zip -r archive.zip dir/        # 压缩为 zip
unzip archive.zip              # 解压 zip
gzip file                      # 压缩文件
gunzip file.gz                 # 解压

包管理

# Debian/Ubuntu (apt)
sudo apt update          # 更新源
sudo apt install nginx   # 安装
sudo apt remove nginx    # 卸载
sudo apt autoremove      # 清理孤立的依赖
apt search keyword       # 搜索包

# CentOS/RHEL (dnf/yum)
sudo dnf install nginx
sudo dnf remove nginx

组合技:管道和重定向

# 管道:前一个命令的输出作为后一个命令的输入
ps aux | grep python | grep -v grep | awk '{print $2}'

# 重定向
command > file          # 输出到文件(覆盖)
command >> file         # 输出到文件(追加)
command 2>&1           # 错误输出重定向到标准输出

# 实战:找出占用磁盘最大的10个文件
du -sh /* 2>/dev/null | sort -rh | head -10

总结

以上命令覆盖了 Linux 日常管理的 90% 场景。建议收藏本文作为速查手册,用的越多越熟练。

Linux 常用命令大全:文件操作、文本处理、网络与系统管理速查手册最先出现在编程·投资·科技

]]>
Docker 从入门到实践:安装、镜像、容器与 Docker Compose 完全指南(2026) https://www.devlearn.club/posts/547 Thu, 21 May 2026 05:49:41 +0000 https://www.devlearn.club/posts/547 什么是 Docker? Docker 是…

Docker 从入门到实践:安装、镜像、容器与 Docker Compose 完全指南(2026)最先出现在编程·投资·科技

]]>
什么是 Docker?

Docker 是一个容器化平台,让你可以把应用及其依赖打包到一个轻量级的「容器」中,在任何 Linux 系统上运行。相比虚拟机,Docker 容器不需要模拟操作系统,启动快(毫秒级)、资源占用少。

安装 Docker

# Ubuntu 24.04 / Debian 12
sudo apt update
sudo apt install ca-certificates curl -y
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y

# 验证安装
sudo docker run hello-world

Docker 基础命令

# 镜像操作
docker pull nginx:latest       # 拉取镜像
docker images                  # 列出本地镜像
docker rmi nginx               # 删除镜像

# 容器操作
docker run -d --name my-nginx -p 8080:80 nginx   # 运行容器
docker ps                     # 查看运行中的容器
docker ps -a                  # 查看所有容器
docker stop my-nginx          # 停止容器
docker start my-nginx         # 启动容器
docker rm my-nginx            # 删除容器

# 进入容器
docker exec -it my-nginx bash

# 查看日志
docker logs -f my-nginx

编写 Dockerfile

# Dockerfile - Python Flask 应用示例
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

构建和运行:

docker build -t my-flask-app .
docker run -d -p 5000:5000 my-flask-app

Docker Compose 多服务编排

# docker-compose.yml
version: '3.8'
services:
  web:
    build: .
    ports:
      - "5000:5000"
    depends_on:
      - db
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: myapp
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:
docker compose up -d     # 启动所有服务
docker compose down      # 停止并删除
docker compose logs -f   # 查看日志

数据持久化:Volume 和 Bind Mount

# Volume(Docker管理)
docker volume create mydata
docker run -v mydata:/app/data myapp

# Bind Mount(宿主机目录)
docker run -v /host/path:/container/path myapp

常用技巧

# 清理未使用的容器/镜像/网络
docker system prune -a

# 查看容器资源占用
docker stats

# 容器间网络通信(同一网络自动 DNS 解析)
docker network create mynet
docker run --network mynet --name web webapp
docker run --network mynet --name db database

常见问题

Q: 不加 sudo 运行 docker 命令?

sudo usermod -aG docker $USER
# 退出重新登录生效

Q: 容器退出后数据会丢失吗?

容器文件系统是临时的,重启或删除容器后数据丢失。重要数据务必使用 volume 或 bind mount 持久化。

总结

Docker 是现代 DevOps 的基石技术。掌握 docker run/build/compose 三板斧,就能应对绝大多数日常开发场景。

Docker 从入门到实践:安装、镜像、容器与 Docker Compose 完全指南(2026)最先出现在编程·投资·科技

]]>
VPS安全加固完整指南:从SSH配置到UFW防火墙策略(2026) https://www.devlearn.club/posts/536 Wed, 20 May 2026 01:02:28 +0000 https://www.devlearn.club/?p=536 VPS安全加固完整指南:从SSH配置到U…

VPS安全加固完整指南:从SSH配置到UFW防火墙策略(2026)最先出现在编程·投资·科技

]]>
VPS安全加固完整指南:从SSH配置到UFW防火墙策略(2026)

买了一块VPS,把默认密码改了就觉得安全了?兄弟,醒醒——互联网上的扫描器24小时不停地在扫22端口,从你装好系统的那一刻起,就有人在尝试暴力破解你的SSH。本神人今天就带你走一遍VPS安全加固的完整流程,从SSH锁死、防火墙配置到入侵检测,一条龙搞定。

第一步:SSH安全加固——锁死大门

SSH是VPS的大门,90%的攻击都是从SSH暴力破解开始的。我们先把它加固到连黑客看了都摇头。

1.1 修改SSH默认端口

默认22端口就是靶子。换个冷门端口,扫描成本直接拉高一个数量级。

$ sudo sed -i 's/^#Port 22/Port 2222/' /etc/ssh/sshd_config
$ sudo systemctl restart sshd
$ sudo systemctl status sshd
● ssh.service - OpenSSH server daemon
     Loaded: loaded (/usr/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2026-05-20 08:30:00 UTC
   Main PID: 12345 (sshd)
     Tasks: 1 (limited: 2280)
     Memory: 1.2M
        CPU: 10ms
     CGroup: /system.slice/ssh.service
             └─12345 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"

重要:在断开当前连接前,另开一个终端测试新端口能连上!

$ ssh -p 2222 user@your-server-ip

1.2 禁止root直接登录

root是每个Linux系统的超管账户,攻击者只要猜对root密码就能为所欲为。禁止root直接SSH登录,先用普通用户登录再sudo提权。

$ sudo adduser vpsadmin
$ sudo usermod -aG sudo vpsadmin
$ sudo sed -i 's/^#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
$ sudo systemctl restart sshd

1.3 配置SSH密钥登录

密码登录再复杂也有被爆破的风险。密钥登录用的是公钥加密,理论上无法暴力破解。

# 本地机器生成密钥对
$ ssh-keygen -t ed25519 -C "vps-$(date +%Y%m%d)"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/user/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/user/.ssh/id_ed25519
Your public key has been saved in /home/user/.ssh/id_ed25519.pub

The key fingerprint is:
SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx vps-20260520

# 上传公钥到VPS
$ ssh-copy-id -p 2222 vpsadmin@your-server-ip
Number of key(s) added: 1

# 禁用密码登录
$ sudo sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
$ sudo sed -i 's/^#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config
$ sudo systemctl restart sshd

1.4 配置fail2ban——自动封禁暴力破解

即使用了密钥登录,也不代表不会有遗落的密码登录服务被攻击。fail2ban会监控SSH日志,发现多次失败尝试后自动封禁IP。

$ sudo apt update && sudo apt install fail2ban -y
$ sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
$ sudo cat /etc/fail2ban/jail.local | grep -A 15 '^\[sshd\]' | head -20
[sshd]
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 5
bantime  = 3600
findtime  = 600

$ sudo systemctl restart fail2ban
$ sudo systemctl status fail2ban
● fail2ban.service - Fail2Ban Service
     Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2026-05-20 08:35:00 UTC
       Docs: man:fail2ban(1)

$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:

第二步:UFW防火墙——设好安检关卡

UFW(Uncomplicated Firewall)是iptables的前端封装,配置简单且功能强大。把不需要的端口统统关掉。

2.1 安装并启用UFW

$ sudo apt install ufw -y
$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing

# 只开放必要的端口(注意替换成你改过的SSH端口)
$ sudo ufw allow 2222/tcp comment 'SSH'
$ sudo ufw allow 80/tcp comment 'HTTP'
$ sudo ufw allow 443/tcp comment 'HTTPS'

$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
2222/tcp                   ALLOW IN    Anywhere
80/tcp                     ALLOW IN    Anywhere
443/tcp                    ALLOW IN    Anywhere
2222/tcp (v6)              ALLOW IN    Anywhere (v6)
80/tcp (v6)                ALLOW IN    Anywhere (v6)
443/tcp (v6)               ALLOW IN    Anywhere (v6)

如果跑数据库(MySQL/PostgreSQL),千万别把3306/5432暴露到公网——绑定到127.0.0.1就好。

2.2 限制SSH来源IP(进阶)

如果你有固定的办公公网IP,可以只允许那个IP访问SSH,其他全部拒绝。这是最高安全策略。

$ sudo ufw delete allow 2222/tcp
$ sudo ufw allow from 203.0.113.0/24 to any port 2222 proto tcp comment 'SSH from office'
$ sudo ufw reload
$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
2222/tcp                   ALLOW IN    203.0.113.0/24
80/tcp                     ALLOW IN    Anywhere
443/tcp                    ALLOW IN    Anywhere

2.3 查看UFW日志

$ sudo tail -n 20 /var/log/ufw.log
May 20 08:40:01 vps kernel: [UFW BLOCK] IN=eth0 OUT= MAC=xx:xx:xx:xx:xx:xx SRC=103.235.46.39 DST=your-ip LEN=40 TOS=0x00 PREC=0x00 TTL=245 ID=54321 PROTO=TCP SPT=44322 DPT=3306 WINDOW=1024 RES=0x00 SYN URGP=0
May 20 08:40:15 vps kernel: [UFW BLOCK] IN=eth0 OUT= MAC=xx:xx:xx:xx:xx:xx SRC=185.220.101.42 DST=your-ip LEN=40 TOS=0x00 PREC=0x00 TTL=241 ID=12345 PROTO=TCP SPT=38295 DPT=6379 WINDOW=65535 RES=0x00 SYN URGP=0

看看这些日志:有人在扫你的3306(MySQL)和6379(Redis)端口。UFW全部挡在外面了。

第三步:系统级安全配置

3.1 自动安全更新

安全漏洞几乎每个月都有新的。配置自动安装安全更新,防止已知漏洞被利用。

$ sudo apt install unattended-upgrades -y
$ sudo dpkg-reconfigure --priority=low unattended-upgrades
# 选择 Yes

$ sudo cat /etc/apt/apt.conf.d/50unattended-upgrades | grep -E "Allowed|auto|Fix|^//" | head -20
Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}";
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
    "${distro_id}ESM:${distro_codename}-infra-security";
};

# 测试配置
$ sudo unattended-upgrades --dry-run --debug 2>&1 | tail -5
2026-05-20 08:45:00,001 INFO No packages found that can be upgraded unattended and no pending auto-removals

3.2 检查监听端口

运行中的服务如果在监听外网端口而你并不知道,那就是个安全隐患。

$ sudo ss -tlnp
State      Recv-Q     Send-Q         Local Address:Port         Peer Address:Port     Process
LISTEN     0          128                  0.0.0.0:2222              0.0.0.0:*         users:(("sshd",pid=12345,fd=3))
LISTEN     0          128                  0.0.0.0:80                0.0.0.0:*         users:(("nginx",pid=23456,fd=6))
LISTEN     0          128                  0.0.0.0:443               0.0.0.0:*         users:(("nginx",pid=23456,fd=7))
LISTEN     0          128                127.0.0.1:3306              0.0.0.0:*         users:(("mysqld",pid=34567,fd=24))

看到区别了吗?SSH、HTTP、HTTPS监听0.0.0.0(所有网卡),这是正常的。但MySQL的3306只绑在127.0.0.1(本地回环)——这才是正确姿势。如果看到0.0.0.0:3306,立刻去改MySQL配置。

3.3 配置日志审计

$ sudo apt install auditd -y
$ sudo auditctl -w /etc/ssh/sshd_config -p wa -k ssh_config_change
$ sudo auditctl -w /etc/passwd -p wa -k user_db
$ sudo auditctl -l
-w /etc/ssh/sshd_config -p wa -k ssh_config_change
-w /etc/passwd -p wa -k user_db

$ sudo ausearch -k ssh_config_change --start today 2>/dev/null | head -20
----
time->Thu May 20 08:30:00 2026
type=PROCTITLE msg=audit(1720000000.123:456): proctitle=736564002D69002F6574632F7373682F737368645F636F6E666967
type=PATH msg=audit(1720000000.123:456): item=0 name="/etc/ssh/sshd_config" inode=78901 dev=08:01 mode=0100644 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0 cap_frootid=0
type=SYSCALL msg=audit(1720000000.123:456): arch=c000003e syscall=82 success=yes exit=0 a0=7ffe3c0f5000 a1=4 a2=1fff a3=7ffe3c0f4610 items=1 ppid=1 pid=1234 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=1 comm="sed" exe="/usr/bin/sed" subj=--- key="ssh_config_change"

第四步:配置rkhunter + chkrootkit——检测入侵

做好了防御,还得有检测手段。rkhunter和chkrootkit是经典的rootkit检测工具。

$ sudo apt install rkhunter chkrootkit -y

# 更新rkhunter数据库
$ sudo rkhunter --propupd
$ sudo rkhunter --check --skip-keypress
[ Rootkit Hunter version 1.4.6 ]

Checking system commands...
  Performing 'strings' command checks
    Checking 'strings' command                           [ OK ]

Checking for rootkits...
  Checking for 'Rootkits'...
    Performing check of known rootkit files and directories
    Checking for 'ADM Worm'                              [ Not found ]
    Checking for 'Ajakit Rootkit'                        [ Not found ]
    ...
  System checks summary
    =====================
    File properties checks...
        Required commands check failed                   [ Warning ]
    
    Rootkit checks...
        Rootkit 'Rootkit'                                [ Not found ]

# chkrootkit快速扫描
$ sudo chkrootkit | grep -v "not infected"
ROOTDIR is '/'
Checking `bifrost'...                                           not found
Checking `console'...                                           not found
...
Checking `lkm'...                                              chkproc: nothing deleted
chkdirs: nothing detected

每周跑一次扫描,或者配置cron自动执行并邮件通知。

第五步:快速安全检查清单

加固完成之后,跑个快速自查确认万无一失:

# 1. 检查SSH配置
$ sudo sshd -T | grep -E "(port|permitrootlogin|passwordauthentication|pubkeyauthentication)"
port 2222
permitrootlogin no
passwordauthentication no
pubkeyauthentication yes

# 2. 检查UFW状态
$ sudo ufw status verbose | head -5
Status: active

# 3. 检查fail2ban状态
$ sudo fail2ban-client status sshd | grep "Total banned"
  |- Total banned:     42

# 4. 检查未授权用户
$ sudo cat /etc/passwd | grep -E "(/bin/bash|/bin/sh)" | grep -v "^#"
root:x:0:0:root:/root:/bin/bash
vpsadmin:x:1000:1000:,,,:/home/vpsadmin:/bin/bash

看到fail2ban已经帮忙封了42个IP了吗?如果没有fail2ban,这42个IP可能还在尝试登录你的服务器。

FAQ:常见问题

改了SSH端口后自己连不上怎么办?

这是最常见的翻车现场。解决方案:大多数VPS提供商(如Linode、DigitalOcean、Vultr)都提供Web控制台(VNC/IPMI),登陆后把端口改回去或检查firewalld/ufw规则。如果连Web控制台也连不上,重启到单用户模式修复。所以务必在修改配置前留一个备用SSH会话。

UFW和firewalld选哪个更好?

Ubuntu/Debian默认用UFW,CentOS/RHEL默认用firewalld。两者底层都是netfilter/iptables或nftables,功能上没有本质区别。建议跟发行版走——Ubuntu用UFW,RHEL系用firewalld。混着用可能造成规则冲突。本文基于Ubuntu 24.04 LTS,所以推荐UFW。

用了SSH密钥登录后还需要配置fail2ban吗?

需要。密钥登录确实很难被破解,但服务器上可能还有其他服务(如Web应用、邮件服务)存在密码登录入口。而且日志里每天几千次失败的SSH连接请求本身就是一种压力。fail2ban能在攻击发生前就阻断IP,减少系统日志噪音,也能在某些密码登录遗留场景中提供防护。一句话:防御深度没有上限

VPS安全加固完整指南:从SSH配置到UFW防火墙策略(2026)最先出现在编程·投资·科技

]]>
Linux systemctl服务管理完全指南:systemd服务的启动、停止、启用与自建服务(2026) https://www.devlearn.club/posts/503 Mon, 18 May 2026 01:02:10 +0000 https://www.devlearn.club/posts/503 Linux systemctl服务管理完…

Linux systemctl服务管理完全指南:systemd服务的启动、停止、启用与自建服务(2026)最先出现在编程·投资·科技

]]>
Linux systemctl服务管理完全指南:systemd服务的启动、停止、启用与自建服务(2026)

前言:为什么需要理解systemctl

管理Linux服务器,最核心的操作就是管理服务。无论是重启Nginx、查看MySQL状态,还是让某个程序开机自启,都离不开 systemctl 命令。现代Linux发行版(Ubuntu 16.04+、Debian 8+、CentOS 7+、RHEL 7+)全面采用 systemd 作为初始化系统,而 systemctl 就是systemd的主控命令。

掌握 systemctl,就等于掌握了Linux服务管理的钥匙。本文从日常操作自建服务单元,覆盖所有高频场景,附真实命令输出,可以直接在你的VPS上验证。

环境准备

  • Linux系统:Ubuntu 22.04/24.04 或 CentOS 7+/Rocky Linux(本文以Ubuntu 24.04演示)
  • sudo或root权限
  • 已安装 systemd(几乎是标配)
$ cat /etc/os-release | head -3
PRETTY_NAME="Ubuntu 24.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"

$ systemctl --version
systemd 255 (255.4-1ubuntu8)
+PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 +PWQUALITY +P11KIT +QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD +BPF_FRAM

一、服务状态管理:查看、启动与停止

1.1 查看所有服务状态

$ systemctl list-units --type=service --all | head -20
  UNIT                          LOAD   ACTIVE SUB     DESCRIPTION
  accounts-daemon.service       loaded active running Accounts Service
  acpid.service                 loaded active running ACPI event daemon
  apparmor.service              loaded active running AppArmor initialization
  apport.service                loaded active running LSB: automatic crash report generation
  atd.service                   loaded active running Deferred execution scheduler
  ...
  nginx.service                 loaded active running A high performance web server and a reverse proxy server

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.

关键字段解读:

  • LOAD:单元定义是否成功加载
  • ACTIVE:高级激活状态(active/inactive)
  • SUB:低层子状态(running/exited/dead等)
  • DESCRIPTION:服务描述

1.2 查看单个服务状态

$ systemctl status nginx.service
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2026-05-18 08:30:12 UTC; 2h 15min ago
       Docs: man:nginx(8)
   Main PID: 1234 (nginx)
      Tasks: 3 (limit: 2345)
     Memory: 8.2M
        CPU: 124ms
     CGroup: /system.slice/nginx.service
             ├─1234 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
             └─1235 nginx: worker process

status 输出中,关键信息

  • Loaded 行显示服务单元路径和是否开机自启(enabled/disabled)
  • Active 行显示当前运行状态和运行时长
  • Main PID 主进程号,排错时非常有用

1.3 启动、停止与重启服务

# 启动服务
$ sudo systemctl start nginx.service
$ systemctl is-active nginx.service
active

# 停止服务
$ sudo systemctl stop nginx.service
$ systemctl is-active nginx.service
inactive

# 重启服务(完整的 Stop + Start)
$ sudo systemctl restart nginx.service

# 重载配置(不中断服务)
$ sudo systemctl reload nginx.service

💡 小技巧: reloadrestart 的区别很重要。修改Nginx配置后,用 reload 可以不中断现有连接完成配置重载,而 restart 会断开所有连接。生产环境优先使用 reload

二、开机自启管理

2.1 启用和禁用开机自启

# 设置开机自启
$ sudo systemctl enable nginx.service
Synchronizing state of nginx.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install enable nginx

# 禁用开机自启
$ sudo systemctl disable nginx.service
Removed /etc/systemd/system/multi-user.target.wants/nginx.service.

# 查看是否开机自启
$ systemctl is-enabled nginx.service
enabled

# 查看所有开机自启的服务
$ systemctl list-unit-files --type=service --state=enabled | head -20
UNIT FILE                              STATE   PRESET
accounts-daemon.service                enabled enabled
atd.service                            enabled enabled
cron.service                           enabled enabled
nginx.service                          enabled enabled
rsyslog.service                        enabled enabled
ssh.service                            enabled enabled
systemd-timesyncd.service              enabled enabled
udisks2.service                        enabled enabled

2.2 启用但不立即启动 / 立即启动但不启用

# 设置开机自启但不立即启动(适合正在维护的服务)
$ sudo systemctl enable --now nginx.service   # 既启用到开机自启,又立即启动
$ sudo systemctl enable --no-start nginx.service  # 仅设置开机自启,不启动
$ sudo systemctl start --no-enable nginx.service   # 立即启动,但不设置开机自启

三、服务日志查看(journalctl)

systemd 统一管理日志,通过 journalctl 查看。这是排查服务崩溃、配置错误的第一工具

# 查看特定服务的日志
$ sudo journalctl -u nginx.service --no-pager | tail -20
May 18 08:30:12 ubuntu-server nginx[1234]: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
May 18 08:30:12 ubuntu-server nginx[1234]: nginx: configuration file /etc/nginx/nginx.conf test is successful
May 18 08:30:12 ubuntu-server systemd[1]: Started A high performance web server and a reverse proxy server.

# 实时追踪日志(类似 tail -f)
$ sudo journalctl -u nginx.service -f

# 查看最近N分钟的日志
$ sudo journalctl -u nginx.service --since "5 min ago"

# 查看今日所有错误级别日志
$ sudo journalctl -p err --since today

# 清理旧日志(释放磁盘空间)
$ sudo journalctl --vacuum-time=7d

💡 对于长期运行的服务器,建议配置 /etc/systemd/journald.conf 中的 MaxUse=500M 限制日志大小,避免日志撑满 /var 分区。

四、实战:自建一个systemd服务单元

这是本文的核心——学会创建自己的 systemd 服务。我们以搭建一个Python 定时数据采集服务为例。

4.1 准备可执行脚本

先创建一个简单的Python脚本:

$ sudo mkdir -p /opt/data-collector
$ sudo tee /opt/data-collector/collect.py << 'EOF'
#!/usr/bin/env python3
import time
import logging
from datetime import datetime

logging.basicConfig(
    filename='/var/log/data-collector.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

def collect_data():
    timestamp = datetime.now().isoformat()
    # 模拟数据采集
    logging.info(f"Data collected at {timestamp}")
    print(f"[{timestamp}] Collection completed")

if __name__ == "__main__":
    while True:
        collect_data()
        time.sleep(300)  # 每5分钟采集一次
EOF

$ sudo chmod +x /opt/data-collector/collect.py

4.2 创建service单元文件

$ sudo tee /etc/systemd/system/data-collector.service << 'EOF'
[Unit]
Description=Data Collector Service - 定时数据采集服务
Documentation=https://example.com/docs
After=network.target network-online.target
Wants=network-online.target

[Service]
Type=simple
User=nobody
Group=nogroup
WorkingDirectory=/opt/data-collector
ExecStart=/usr/bin/python3 /opt/data-collector/collect.py
Restart=always
RestartSec=10
StartLimitInterval=300
StartLimitBurst=5

# 安全加固
ProtectSystem=strict
ReadWritePaths=/var/log
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
EOF

单元文件字段详解:

  • [Unit] 段:描述信息和依赖关系。
  • After=network.target 确保网络就绪后启动。
  • Type=simple 表示 ExecStart 启动的进程就是主进程(最常用)。
  • Restart=always 进程退出后自动重启,无人值守必备。
  • RestartSec=10 重启前等待10秒,避免频繁重启。
  • ProtectSystem=strict 保护系统文件不被服务进程意外修改。
  • [Install] 段定义何时启用(通常是 multi-user.target)。

4.3 加载并启动自定义服务

# 重新加载 systemd 配置(每次修改service文件后必做)
$ sudo systemctl daemon-reload

# 启用开机自启
$ sudo systemctl enable data-collector.service
Created symlink /etc/systemd/system/multi-user.target.wants/data-collector.service → /etc/systemd/system/data-collector.service.

# 启动服务
$ sudo systemctl start data-collector.service

# 查看状态
$ sudo systemctl status data-collector.service
● data-collector.service - Data Collector Service - 定时数据采集服务
     Loaded: loaded (/etc/systemd/system/data-collector.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2026-05-18 10:00:00 UTC; 5s ago
   Main PID: 5678 (python3)
      Tasks: 1 (limit: 2345)
     Memory: 12.3M
        CPU: 45ms
     CGroup: /system.slice/data-collector.service
             └─5678 /usr/bin/python3 /opt/data-collector/collect.py

4.4 服务故障排查

# 启动失败时查看详细日志
$ sudo journalctl -u data-collector.service --no-pager

# 查看服务启动超时
$ sudo systemctl show data-collector.service | grep Timeout
TimeoutStartUSec=1min 30s
TimeoutStopUSec=1min 30s

# 手动测试 ExecStart 命令是否正常工作
$ sudo -u nobody /usr/bin/python3 /opt/data-collector/collect.py

五、systemctl进阶操作

5.1 屏蔽与取消屏蔽服务

# 屏蔽服务(防止手动和自动启动)
$ sudo systemctl mask nginx.service
Created symlink /etc/systemd/system/nginx.service → /dev/null.

# 取消屏蔽
$ sudo systemctl unmask nginx.service
Removed /etc/systemd/system/nginx.service.

maskdisable 更彻底——disable只是移除自启符号链接,仍然可以手动 start;而 mask 将服务链接到 /dev/null,任何启动尝试都会被忽略。

5.2 查看服务依赖树

# 查看某服务依赖了哪些其他服务
$ systemctl list-dependencies nginx.service
nginx.service
● ├─system.slice
● ├─network.target
● └─sysinit.target
●   └─systemd-sysusers.service

# 查看反向依赖(哪些服务依赖本服务)
$ systemctl list-dependencies --reverse nginx.service
nginx.service
● └─multi-user.target
●   └─graphical.target

5.3 管理target(运行级别)

# 查看当前运行的target
$ systemctl get-default
multi-user.target

# 切换到救援模式
$ sudo systemctl rescue

# 切换到图形界面模式
$ sudo systemctl isolate graphical.target

# 设置默认target
$ sudo systemctl set-default multi-user.target
Removed /etc/systemd/system/default.target.
Created symlink /etc/systemd/system/default.target → /lib/systemd/system/multi-user.target.

常见问题(FAQ)

Q1: systemctl start 后服务立即退出了,状态显示 inactive(dead),怎么办?

这通常是 Type 设置不当。如果程序启动后 fork 到后台运行(像 Nginx 的 daemon on),应使用 Type=forking 并设置 PIDFile。如果程序不会长时间驻留(执行完就退出),用 Type=oneshot。常规长时间运行的程序用 Type=simple。查看日志 journalctl -u your-service.service -xe 通常能直接定位原因。

Q2: 修改了.service文件后需要执行什么命令?

每次修改 /etc/systemd/system/*.service 后,必须执行 sudo systemctl daemon-reload 让systemd重新加载配置。然后再 restart 服务。如果不执行 daemon-reload,systemd 仍然使用旧配置,修改不会生效。这是一个非常常见的踩坑点。

Q3: 如何让服务在崩溃后自动重启?

在 service 文件的 [Service] 段加上 Restart=alwaysRestartSec=5。systemd 会自动重启退出的进程。还可以设置 StartLimitInterval=600StartLimitBurst=5 来限制10分钟内最多重启5次,防止无限重启导致系统资源耗尽。

Q4: systemctl enable 和 systemctl start 有什么区别?

enable 创建符号链接让服务在开机时自动启动,不影响当前运行状态start 立即启动服务,不影响开机自启设置。通常首次部署时需要 enable + start 两个命令都执行,或者用 enable --now 一步到位。日常管理重启用 restart,配置变更用 reload

总结

本文全面覆盖了 Linux systemctl 的核心操作:

  • 服务状态管理:start/stop/restart/reload/status
  • 开机自启控制:enable/disable/is-enabled
  • 日志追踪:journalctl 查看和排查服务问题
  • 自建服务单元:从零创建可自启的 systemd 服务
  • 进阶技巧:mask屏蔽、依赖树分析、target切换

掌握 systemctl 是Linux运维的基本功。建议在自己的VPS上动手练习一遍,尤其是 自建service文件 的流程——这在实际生产环境中会经常用到。

延伸阅读:

Linux systemctl服务管理完全指南:systemd服务的启动、停止、启用与自建服务(2026)最先出现在编程·投资·科技

]]>