我有一个很深的体会:Linux 下那些看似不起眼的“账号小配置”,平时没人关心,真出问题的时候往往能把人折腾到半夜。密码过期时间就是典型例子。某次我被拉去处理一个线上故障,定时任务连续几天没跑,检查 crontab 日志全是权限报错,最后才发现是执行任务的账号密码早就过期,服务彻底丧失了登录认证能力。另一次更离谱,给新员工建完账号,第二天对方说登不进服务器,我登上去一看,临时密码确实改过了,但邮箱里堆满系统发来的密码过期提醒——因为创建账号时没有把密码策略调整到“首次登录必须修改”的状态,新旧策略叠加到一起,账号直接在第一次认证时就被卡住。
在 Linux 环境里,查看和修改用户密码过期时间,说到底就是围绕chage、passwd这两个命令,外加一个很关键的账号文件/etc/shadow来操作。只要把这三个东西的原理和用法吃透,不管是要临时看某个账号还能不能用,还是要批量给几百个账号统一设置密码策略,都能快速搞定。这篇文章我按照自己的实操习惯,从底层逻辑、查看方法、修改参数、生产环境落地,再到故障排查,完整梳理一遍,希望能让你少踩几个坑。
1. 搞清楚密码过期时间的底层逻辑
1.1 /etc/shadow 才是真正的“账本”
很多刚开始接触 Linux 的同学会默认密码信息在/etc/passwd里,这个方向其实只对了一半。/etc/passwd里保存的是用户的基础信息,比如用户名、UID、主目录、登录 Shell,而真正存储密码摘要和密码策略的,是权限更严格的/etc/shadow文件。/etc/shadow只能用 root 或具备 sudo 权限的账号读取,普通用户是看不到的。
用sudo cat /etc/shadow查看,一行对应一个用户,典型内容长这样:
oracle:$6$YKzGtQrB$aT1k9...:19905:10:90:7:30::这串内容用冒号分隔成 9 个字段,含义分别如下:
| 字段位 | 含义 | 示例值 |
|---|---|---|
| 1 | 用户名 | oracle |
| 2 | 加密后的密码摘要 | $6$YKzGtQrB$aT1k9... |
| 3 | 最后一次修改密码的日期,从 1970-01-01 起算的天数 | 19905 |
| 4 | 两次修改密码之间的最小间隔天数 | 10 |
| 5 | 密码最长有效天数,超过则密码过期 | 90 |
| 6 | 过期前提前提醒的天数 | 7 |
| 7 | 密码过期后还能宽限登录的天数,超过则账号禁止登录 | 30 |
| 8 | 账号过期日期,同样从 1970-01-01 起算,空值表示永不过期 | 空 |
| 9 | 保留字段 | 空 |
第3字段等于 19905,换算成日期大概是2024-07-01,也就是说该账号最后一次改密码是在这一天。如果第5字段是 90,那么第 90 天后,也就是2024-09-29,密码进入过期状态。这些字段就是 Linux 判断“密码过期时间”的原始依据。
1.2 密码过期和账户过期是两个概念
这是非常多新手容易混淆的地方。网络上不少教程把“密码过期”和“账户过期”混为一谈,但真实场景里它们完全是两回事。
密码过期,指的是密码本身失效。账号还在,用户也还存在,只是当前密码不允许继续使用,用户登录时会被要求立即重设密码。如果一切正常,重设密码后账号立刻恢复使用。
账户过期,指的是该账号整体失效,无论密码是否正确,都不允许再登录。这在/etc/shadow里对应第8字段,在chage -l里体现为Account expires。
判断一张 Linux 账号是否还能用,不能只看密码有没有过期,还要看账户本身有没有被“作废”。尤其是外包人员、临时协作账号,很多公司习惯直接用账户过期时间来实现“到点自动失效”,而不是手动一个个删。后面讲修改方法时,我会重点演示如何设置和清除账户过期时间。
1.3 默认策略是从哪来的
新创建的用户,密码策略不是凭空生成的,它的默认值来自/etc/login.defs文件里的几个参数:
PASS_MAX_DAYS 90 PASS_MIN_DAYS 0 PASS_WARN_AGE 7PASS_MAX_DAYS是密码最长有效天数,PASS_MIN_DAYS是最小修改间隔,PASS_WARN_AGE是过期前提醒天数。很多业内常见的做法是把PASS_MAX_DAYS设置为 90,也就是三个月强制改一次密码,这是为了满足等保、ISO 27001 或者其他安全合规要求。而某些个人开发机或者内部实验环境,默认可能是 99999,约等于永不过期。
这里要特别说一句,改/etc/login.defs只影响之后新建的用户,对已有的存量用户不生效。已经有用户的密码策略,还是需要逐个或批量用chage去调整。
2. 查看用户密码过期时间的实操方法
2.1 首选命令 chage -l
查看单个用户的密码过期时间,最直观的命令是chage -l。比如我想看 oracle 用户的密码状态:
sudo chage -l oracle输出是这样的:
Last password change : Jul 01, 2024 Password expires : Sep 29, 2024 Password inactive : Oct 29, 2024 Account expires : never Minimum number of days between password change : 10 Maximum number of days between password change : 90 Number of days of warning before password expires : 7每一行含义都很直白。Last password change是最后一次改密码日期;Password expires是密码预计过期日期;Password inactive是密码过期后还能宽限使用的最后日期,超过这个日期账号就不能再登录了;Account expires是账户整体失效日期,never表示永不过期。
下方三行对应最小天数、最大天数、提醒天数。看到这些信息,基本就能判断一个账号目前处于什么状态:密码是否快到期,是否有宽限期,账户是否被设置了失效时间。
2.2 passwd -S 和直接查看 shadow
如果不想看那么详细的输出,也可以用passwd -S快速了解状态:
sudo passwd -S oracle输出类似:
oracle P 07/01/2024 10 90 7 30从左到右分别表示:用户名、密码状态、最后修改日期、最短修改天数、最长有效天数、提前提醒天数、宽限天数。这里的密码状态位常见有三种:P表示密码可用,L表示密码被锁定,NP表示该用户没有设置密码。
还有一种方式就是直接查看/etc/shadow文件。虽然字段不如前两种友好,但在某些脚本场景下反而更直接,因为可以一次性带出所有字段,方便用awk继续处理:
sudo awk -F: 'NR>=1{print $1,$5,$6,$7,$8}' /etc/shadow看到这里你会理解,chage -l展示的“密码过期时间”,本质上就是/etc/shadow里第五个字段的天数换算出来的。掌握 shadow 的字段含义,会更容易理解各种命令输出之间的对应关系。
2.3 批量盘点所有用户
单个用户用好chage -l就行,但如果你管理的是几十上百个账号,一个个手动敲命令效率太低。这时候建议写个小循环,把需要关注的账号捞出来,统一打印关键信息。
我常用的一个脚本片段是这样:
for user in $(getent passwd | awk -F: '$3>=1000 && $3<65534 {print $1}'); do echo "===== $user =====" sudo chage -l "$user" | grep -E "Last password change|Password expires|Account expires" done用getent passwd而不是直接读/etc/passwd,好处是当系统接入了 LDAP、NIS 或者其他认证源时,也能把远端用户一起查出来,不会漏掉。过滤条件里的 UID 范围可以根据实际情况调整,目的是排除系统服务账号。
批量盘点的意义在于提前发现风险账号。我建议每隔一个月跑一次,把即将过期的账号列表拉出来,该通知的提前通知,该重置的提前处理,别等到用户投诉了才去翻日志。
3. 修改用户密码过期时间:参数与场景
3.1 chage 命令的关键参数
修改密码过期时间,chage依然是首选。命令格式是:
chage [选项] 用户名常用参数如下:
| 参数 | 作用 |
|---|---|
| -m | 修改密码的最小间隔天数,两次修改之间至少相隔多少天 |
| -M | 密码最大有效天数,这是最常用的参数 |
| -W | 密码过期前多少天开始提醒 |
| -I | 密码过期后宽限多少天,超过宽限期账号锁定 |
| -E | 设置账户过期日期,可以直接写YYYY-MM-DD,也可以用天数 |
| -d | 把“最后修改密码日期”改成指定日期,0 表示立即过期 |
单独看参数可能觉得抽象,结合具体场景就好理解了。
3.2 常见修改场景演示
场景一:给现有用户设置 90 天密码有效期,提前 7 天提醒,密码最短使用 0 天,过期后宽限 30 天。
sudo chage -M 90 -m 0 -W 7 -I 30 oracle执行后再次查看:
sudo chage -l oracle能看到最大天数变成 90,提醒天数变成 7。这个场景一般用于安全基线整改,把公司内部所有人工账号统一调到 90 天过期。
场景二:强制用户下次登录时修改密码。运维在处理初始密码或临时密码时经常用:
sudo chage -d 0 oracle把“最后修改日期”设为 0,系统会认为密码已经严重过期,用户下次登录时必须先重置密码,之后才能进入会话。这个操作比手动改密码再告诉用户“你改一下”更稳妥,因为系统层面强制执行,用户没办法跳过。
场景三:设置账户在某天整体失效,比如临时账号只需要用到年底:
sudo chage -E 2024-12-31 tempuser这个操作的场景很明确:外包协作、临时维护、项目授权,时间到了账号自动不可用,不需要管理员记着去删除。
场景四:取消所有过期限制,让账号“永不过期”:
sudo chage -M 99999 -E -1 oracle-E -1会清除账户过期日期,-M 99999相当于把密码有效期拉到非常长。这个方法一般只建议用在服务账号、机器账号上,人工账号建议保留合理的有效期。
3.3 用 passwd 完成部分修改
passwd命令也能设置部分密码老化参数,只是不像chage那么全。常用写法是:
sudo passwd -x 90 -w 7 -i 30 oracle-x对应最大有效天数,-w对应提前提醒天数,-i对应宽限天数。用passwd的好处是命令更短,适合临时执行;但像账户过期时间-E、强制过期-d这样的能力,还是得用chage来实现。所以我的建议是,统一用chage管理,避免两套命令混着用导致混乱。
3.4 修改后的验证建议
修改完策略以后,不要急着走人,先确认一手:
sudo chage -l oracle重点看Password expires和Account expires两行是否符合预期。另外要注意,修改是立即生效的,不需要重启服务,也不需要重启机器。但如果用户当前已经处于登录状态,老的会话不会因为策略变更被强制踢出,新登录才会受新策略约束。
4. 生产环境中的策略落地与自动化
4.1 新员工账号:首登强制修改
给新员工建账号,正确流程不是设个密码就完事,而是把“首登强制修改密码”这个动作做进去。否则就会出现开头提到的尴尬情况:临时密码有效期没设置好,用户还没来得及登录,密码就过期了。
推荐的创建流程是:
sudo useradd -m -s /bin/bash zhangsan echo "Temp@12345" | sudo passwd --stdin zhangsan sudo chage -d 0 zhangsan sudo chage -M 90 -m 0 -W 7 zhangsan第一行创建用户,第二行写入临时密码,第三行让密码立即过期,第四行设置常规的 90 天有效期和提醒策略。用户首次登录时会被强制要求设置新密码,之后按照 90 天有效期正常轮换。
4.2 批量整改存量账号
公司在做安全基线整改时,经常需要对一批账号统一设置密码策略。假如有一份user_list.txt,每行一个用户名,脚本可以这样写:
#!/bin/bash while read -r user; do if id "$user" >/dev/null 2>&1; then chage -M 90 -m 0 -W 7 "$user" echo "$user 策略更新完成" else echo "$user 不存在" fi done < /root/user_list.txt脚本里的id "$user"用来判断用户是否存在,避免因为拼写错误或者已删除账号导致命令执行报错。批量操作之前,一定要先在测试环境跑一遍,或者先拿两三个账号试点,确认逻辑无误后再全量执行。这个习惯不是小题大做,而是避免把生产环境账号策略一次性改错的兜底手段。
4.3 到期前预警脚本
管理的账号一多,光靠人脑记着谁哪天过期不现实。更好的方案是用脚本定期扫描,提前把即将过期的账号列出来,再通过邮件、企业微信消息等方式通知管理员。
我写过的一个相对简单的预警脚本如下:
#!/bin/bash # 密码过期预警脚本,建议配合 crontab 每天执行一次 WARN_DAYS=7 TODAY_EPOCH=$(date +%s) EXPIRED_SOON=() for user in $(getent passwd | awk -F: '$3>=1000 && $3<65534 {print $1}'); do expire_str=$(sudo chage -l "$user" | awk -F': ' '/Password expires/{print $2}') if [ "$expire_str" = "never" ] || [ -z "$expire_str" ]; then continue fi expire_epoch=$(date -d "$expire_str" +%s) diff_days=$(( (expire_epoch - TODAY_EPOCH) / 86400 )) if [ "$diff_days" -le "$WARN_DAYS" ] && [ "$diff_days" -ge "0" ]; then EXPIRED_SOON+=("$user 还剩 ${diff_days} 天过期") fi done if [ ${#EXPIRED_SOON[@]} -gt 0 ]; then printf '%s\n' "${EXPIRED_SOON[@]}" | mail -s "密码即将过期的账号提醒" ops@example.com fi脚本中核心是把Password expires的文本日期通过date -d转成时间戳,再计算与当前时间差。这种方案的优点是跨平台兼容性好,如果直接用/etc/shadow里的天数计算,还得额外处理“永不过期”和空字段各种边界情况。
配合 crontab 使用时,可以每天上午 9 点跑一次:
0 9 * * * /usr/local/sbin/password_expire_check.sh >> /var/log/password_expire_check.log 2>&1日志写到独立文件里,后面排查问题更方便,也方便确认脚本是否真的被执行。
4.4 服务账号与安全边界
批量设置密码策略时,一定要小心服务账号。像mysql、nginx、redis这类程序运行账号,很多是靠密钥文件或服务配置启动的,密码过期并不影响服务正常运行,但如果某些脚本依赖 su 切换身份,密码过期就会出现偶发失败。
服务账号建议单独维护一套策略:要么不做密码有效期限制,要么用chage -M 99999拉长有效期,同时通过密钥认证的方式管理,避免密码口令暴露在脚本和配置文件里。我给服务账号做个备注,统一规定“不参与人工账号的密码过期策略”,省得每到三个月整改期,服务账号也跟着被迫改密码,引发不必要的故障。
5. 常见问题与排错实录
5.1 改了策略为什么不生效
比较常见的情况是,管理员用chage -M 90给存量用户改了策略,但在/etc/login.defs里看到默认值还是 99999,就以为没生效。实际上chage命令修改的是用户层面的策略,优先级高于/etc/login.defs的默认配置,修改完成以后,用chage -l立刻能看到变化。“不生效”的错觉多半是没有正确解读输出,或者忘记用sudo导致命令其实执行失败了。
另一种情况更隐蔽:如果用户是通过 LDAP 或 NIS 集中认证,Linux 本地的/etc/shadow可能根本没有该用户的记录,chage命令无从改起。这时候需要在中心目录服务上做策略调整,而不是单台服务器上折腾。
5.2 账号状态判断不清导致登录失败
SSH 登录时报Your password has expired,这种情况一般说明密码确实已过期,但账号还算“活着”,用户有机会在登录流程中直接修改密码。如果报的是Account is locked或者Account expired,就要重点检查两个方向:一是 shadow 密码字段是否以!开头,二是账户过期日期是否已经抵达。
我整理过一个快速诊断表,平时排查时对照着看效率很高。
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| Password has expired | 密码有效期到了 | 登录时按提示重置,或管理员用chage -d 0强制重置 |
| Account is locked | 密码字段以!开头 | sudo passwd -u 用户名解锁 |
| Account expired | 账户过期日期已到 | sudo chage -E -1 用户名清除过期限制 |
| Cannot set password | 最小间隔天数限制 | 调小或清零最小天数:sudo chage -m 0 用户名 |
| 密码正确但登录失败 | 可能是认证方式被 PAM 限制 | 检查/etc/security/下对应模块配置 |
5.3 快速诊断速查表
排错现场最忌讳来回试命令,效率太低。我建议把下面的命令组合记熟,基本能覆盖九成以上的密码过期问题:
查看单个用户完整策略:
sudo chage -l 用户名强制用户下次登录改密码:
sudo chage -d 0 用户名设置 90 天有效期和 7 天提醒:
sudo chage -M 90 -W 7 用户名清除账户过期限制:
sudo chage -E -1 用户名查看账号是否被锁定:
sudo awk -F: '/^用户名:/{print $2}' /etc/shadow如果第2字段以!或*开头,说明密码锁定状态有问题,需要先用passwd -u解锁。
5.4 我的几条实操纪律
和密码过期时间打交道久了,我慢慢形成了几条固定习惯,写在这里供你参考。
第一,账号创建时就把策略定好,别留着默认配置。临时密码、首次强制修改、有效期这三个动作必须一次到位。第二,服务账号单独维护,不参与人工账号的统一过期策略,避免三个月一改的节奏干扰线上服务。第三,预警脚本要固定执行周期,并且把执行日志留好,不仅为了发现问题后追溯,也为了让自己安心——有没有真正跑过,日志一眼就能看出来。第四,每次修改完策略,都要重新执行chage -l看一眼输出。这看起来是重复动作,但恰恰是避免“改了等于没改”的最有效方法。
密码过期时间看似是个小配置,但它直接决定了账号能不能继续使用,也关系到账号安全策略能否落地。把这里的逻辑和命令真正吃透,不管是日常管理还是应急排障,都会顺手很多。