服务器被暴力SSH爆破,如何防护与封禁IP

前言

公网服务器的22端口每天都会被机器人字典爆破,日志里满是 Failed password。口令弱就容易被撞库拿到root。本篇从"先排查→临时封禁→加固配置→自动防护"给出一套可落地的做法。

一、先确认是不是被爆破

# 统计失败次数最多的IP
lastb | awk '{print $3}' | sort | uniq -c | sort -rn | head
# 或看secure日志里的失败记录
grep "Failed password" /var/log/secure | awk '{print $11}' | sort | uniq -c | sort -rn | head

出现大量同一IP的失败尝试,就是爆破。

二、手动封禁恶意IP(应急)

firewalld 临时/永久拉黑:

# 临时封禁
firewall-cmd --add-rich-rule='rule family=ipv4 source address=1.2.3.4 reject'
# 永久封禁
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=1.2.3.4 reject'
firewall-cmd --reload
# 查看已加规则
firewall-cmd --list-rich-rules
# 解除封禁
firewall-cmd --permanent --remove-rich-rule='rule family=ipv4 source address=1.2.3.4 reject'

也可用 iptables -A INPUT -s 1.2.3.4 -j DROP。

三、加固sshd配置(根本)

编辑 /etc/ssh/sshd_config:

# 修改默认端口,别再用22
Port 2222
# 禁止root直接密码登录
PermitRootLogin no
# 禁用空密码
PermitEmptyPasswords no
# 建议改用密钥登录后关闭密码
PasswordAuthentication no
# 最大尝试次数
MaxAuthTries 3
# 10分钟无活动断开
ClientAliveInterval 600

改完 systemctl restart sshd,先保留当前会话再重启,防止把自己锁外面;新端口记得在防火墙放行。

四、自动防爆:fail2ban

自动统计失败次数并封IP,最省事:

yum install -y fail2ban fail2ban-firewalld

新建 /etc/fail2ban/jail.local:

[sshd]
enabled = true
port = 2222
maxretry = 5
findtime = 600
bantime = 86400

启动并自启:

systemctl enable --now fail2ban
# 查看封禁名单
fail2ban-client status sshd

五、常见坑

  1. 改端口只改sshd忘了防火墙放行:自己也连不上,务必先 firewall-cmd --permanent --add-port=2222/tcp;
  2. 关密码登录前没配好密钥:直接被锁门外;
  3. 只封一次IP:机器人换段继续扫,要用fail2ban自动封;
  4. 云安全组没限制:在安全组只放行你常用IP访问SSH端口,比本机防护更前置;
  5. 弱密码root:PermitRootLogin no + 密钥才是正解,改端口只是提高门槛。

总结

应急用firewalld拉黑,长期靠改端口、禁root密码、上密钥,再用fail2ban自动封高频失败IP,云安全组白名单兜底。层层叠加,爆破就很难成功。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-20 07:29 

Linux查看CPU内存负载,top命令详解

前言

服务器变慢、网站卡顿,第一反应是看 CPU 和内存谁吃满了。top 是 Linux 自带的实时监控命令,能动态看到系统负载和每个进程的占用。本篇讲清楚 top 界面每一行含义、常用按键,以及怎么判断到底是 CPU 满还是内存满。

一、启动top

top

进入动态刷新界面,默认每3秒刷新一次,按 q 退出。

二、顶部系统信息各行含义

  • 第一行:load average 三个数分别是1分钟、5分钟、15分钟平均负载,数值超过CPU核数说明忙不过来;
  • 第二行 Tasks:进程总数、运行中、睡眠、停止、僵尸进程数;
  • 第三行 CPU:%us 用户进程占用、%sy 系统内核占用、%id 空闲,id接近0说明CPU紧张;
  • 第四行 Mem:物理内存总量、已用、空闲、缓存;
  • 第五行 Swap:交换分区使用,频繁交换说明内存不足。

看核数判断负载:

nproc
# 假设2核,load average长期超过2就要警惕

三、进程列表关键列

  • PID 进程号;
  • %CPU CPU占用百分比;
  • %MEM 内存占用百分比;
  • RES 进程实际占用物理内存(KB);
  • COMMAND 启动命令。

四、常用交互按键

按键 作用
P 按CPU占用降序
M 按内存占用降序
1 展开每个核心单独显示
k 输入PID杀掉进程
q 退出
# 静态一次性输出top结果(适合脚本/采集)
top -bn1 | head -20
# 只看某个进程
top -p PID

五、判断思路

  1. CPU高:按P排序,找%CPU高的进程,常见是死循环PHP、爬虫、计算任务;
  2. 内存高:按M排序看%MEM和RES,常见内存泄漏、缓存过大;
  3. 负载高但CPU不高:可能是大量I/O等待或僵尸进程,配合 iostat、vmstat 看;
  4. Swap被大量使用:物理内存不够,系统把内存换到磁盘,会明显变慢。

常见坑

  1. 把load average绝对值当指标:要结合核数,1核负载3和8核负载3完全不同;
  2. 只看%CPU不看持续时间:瞬时冲高正常,持续90%以上才是问题;
  3. RES和SHR混淆:RES是独占物理内存,判断内存占用看RES;
  4. 僵尸进程多:Z 状态,父进程没回收,需要找父进程处理。

总结

top 里按P看CPU、按M看内存,先看顶部load和核数是否匹配,再定位具体进程。CPU满找%CPU高的,内存满找%MEM高的,再决定优化或kill。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-20 07:28 

服务器SSH连接慢,DNS反向解析问题解决办法

前言

用 ssh 登录服务器,输入密码前要等好几秒甚至十几秒才出现密码提示,连上之后命令回显也卡。很多人以为是网络差,其实最常见原因是 sshd 在做 DNS 反向解析,解析超时才放行。本篇给出关闭反向解析的标准做法。

一、为什么会慢:DNS反向解析

客户端连上后,sshd 默认会把客户端 IP 反查成主机名(UseDNS yes)。如果服务器配的 DNS 解析不了这个公网 IP,就一直等到超时,表现为卡在登录前。

二、关闭sshd反向解析(核心)

编辑配置文件:

vi /etc/ssh/sshd_config

找到或新增两行:

UseDNS no
GSSAPIAuthentication no
  • UseDNS no:关闭IP反查;
  • GSSAPIAuthentication no:关闭GSSAPI认证,这也会触发额外DNS查询导致卡顿。

三、重启sshd生效

systemctl restart sshd
# CentOS7 服务名是 sshd,部分发行版是 ssh
systemctl status sshd

注意:重启前保留一个已登录的会话,避免配置写错把自己锁在外面。

四、其他导致SSH慢的因素

  1. DNS配置本身慢:检查 /etc/resolv.conf,解析超时的nameserver会拖慢所有服务,可临时改成公共DNS测试;
  2. 反向解析写进hosts:不想关UseDNS也可在 /etc/hosts 里把常用客户端IP和主机名写死,跳过查询;
  3. GSSAPI:客户端侧也可加 -o GSSAPIAuthentication=no 临时绕过;
  4. 登录banner/反向PTR:某些版本还会尝试获取PTR记录,关UseDNS一并解决。

五、验证是否改善

# 客户端用详细模式看卡在哪一步
ssh -v root@服务器IP

观察卡在 Trying reverse mapping / GSSAPI 字样的位置,关掉对应项后应秒出密码提示。

常见坑

  1. 改完不重启sshd:配置不生效;
  2. 改错文件:是 sshd_config(服务端),不是 ssh_config(客户端);
  3. 重启时断开当前连接:保留旧会话再操作,防止锁死;
  4. 公司内网IP也被反查:UseDNS no 对所有连接生效,不影响安全登录。

总结

SSH登录慢九成是 UseDNS yes 反向解析超时,改 /etc/ssh/sshd_config 为 UseDNS no、GSSAPIAuthentication no,重启sshd即可秒连。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-20 07:27 

Linux crontab定时任务不执行常见原因与排查

前言

crontab 写好了却到点不执行,是服务器运维最常见的坑之一。手动跑脚本正常,定时任务却没反应,问题往往不在脚本本身,而在服务状态、环境变量、时间格式和权限。本篇按从易到难的顺序列出排查点。

一、先确认crond服务在运行

systemctl status crond
# 没启动就启动并设开机自启
systemctl start crond
systemctl enable crond

服务没跑,所有定时任务都不会触发,这是第一个要查的。

二、检查定时任务格式是否正确

标准格式:分 时 日 月 周 命令

# 查看当前用户的定时任务
crontab -l
# 例如:每10分钟执行一次脚本
*/10 * * * * /usr/bin/php /www/backup.php

常见格式错误:分钟位置写成了小时、用了 * 却想表示"第1个"、命令路径用了相对路径。

三、查看cron日志,判断有没有触发

# CentOS7 日志位置
tail -f /var/log/cron

到点看日志里有没有出现这条任务:

  • 日志里根本没有:格式没被识别、服务没跑、或时间写错;
  • 日志有执行记录但没效果:是脚本/环境问题,往下查。

四、脚本环境变量问题(高频)

cron 的环境比手动登录干净得多,很多命令找不到、相对路径失败。解决:

  1. 用绝对路径:脚本里和任务里都写全,比如 /usr/bin/php、/www/backup.php;
  2. 开头加载环境:
    */10 * * * * source /etc/profile; /usr/bin/php /www/backup.php
  3. 先手动用cron环境复现:env -i /bin/bash script.sh 模拟干净环境跑一遍。

五、权限与重定向

  • 脚本要有执行权限:chmod +x /www/backup.sh;
  • 脚本没加 shebang(#!/bin/bash)会找不到解释器;
  • 把错误输出留下来方便定位:
    */10 * * * * /usr/bin/php /www/backup.php >> /www/log/cron.log 2>&1

    加了 2>&1,报错就写进日志,不再"静默失败"。

六、其他易漏点

  1. 时区不一致:timedatectl 看系统时区,容器里时间错会导致任务错位;
  2. 日和周同时指定是"或"关系,容易误触发,注意语义;
  3. root和普通用户任务混淆:sudo crontab -l 看root的,普通用户看自己的;
  4. 磁盘满了:日志写不进去,任务看似没执行。

总结

排查顺序:crond服务是否运行 → 任务格式对不对 → /var/log/cron 有没有触发 → 绝对路径和环境变量 → 执行权限与错误重定向 → 时区。手动正常、定时不跑,九成是环境变量和绝对路径问题。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-20 07:26 

Linux磁盘占用100%怎么排查,大文件查找清理方法

前言

服务器磁盘爆满到100%,会导致网站写不了日志、数据库报错、服务异常退出。很多人只看到根目录满,却找不到到底是谁占的。本篇按"先看分区→再找大目录→再找大文件→安全清理"的顺序给出可直接执行的命令。

一、先看哪个分区满了

df -h

看 Use% 列,找到达到100%的挂载点(常见是 / 根分区或 /data)。注意区分分区,别在没满的分区里瞎找。

二、逐级定位占用最大的目录

# 从根目录开始,显示一级目录大小,排序,排除挂载点干扰
du -h --max-depth=1 / 2>/dev/null | sort -rh | head

进入最大的那个目录重复执行,比如:

du -h --max-depth=1 /var 2>/dev/null | sort -rh | head

一层层往下钻,直到定位到具体目录。

三、查找超过指定大小的大文件

# 找出/var下大于500MB的文件
find /var -type f -size +500M -exec ls -lh {} \; 2>/dev/null | awk '{print $5, $9}'
# 全盘找大于1G的文件
find / -type f -size +1G 2>/dev/null | xargs ls -lh

常见大头:日志文件、MySQL binlog、Docker镜像/容器、上传目录、core dump、旧备份。

四、安全清理方式

  1. 日志文件:确认无用后清空(不要直接删正在写的文件,空间不释放):
    # 清空而不是删除,释放空间且进程句柄仍在
    > /var/log/nginx/access.log
  2. 已删除但仍被进程占用的文件(df满但du对不上):
    lsof | grep deleted
    # 找到对应PID后重启该服务释放句柄
  3. Docker清理:
    docker system df          # 先看占用
    docker system prune -a    # 清理无用镜像、停止容器、悬空卷
  4. 旧备份/binlog:确认可删后 rm -f,MySQL binlog 用 PURGE BINARY LOGS 正规清理。

五、常见坑

  1. 直接rm正在写入的大日志:空间不立刻释放,要 > 文件 清空或重启进程;
  2. df满du小:典型是已删除文件被进程占用,用 lsof | grep deleted 找;
  3. 只删根目录表面:--max-depth=1 逐级下钻才找得到真凶;
  4. 误删数据库文件/系统日志:清理前先 ls -lh 确认,重要数据先备份;
  5. Docker不清理:镜像层和停止容器很容易吃掉几十G,docker system prune 定期跑。

总结

df -h 找满的分区,du --max-depth=1 逐级定位,find -size 找大文件,日志用清空、Docker用prune、被占用文件重启释放。按这条链路,100%磁盘很快能查清楚。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-20 07:26 

CentOS7关闭SELinux详细步骤,临时与永久关闭

前言

CentOS7 默认开启 SELinux,它是一套强制访问控制机制,经常和自行搭建的 Nginx、PHP-FPM、网站目录权限冲突,表现为页面 403、文件写不进去、代理被拦截。生产环境如果不想精细配置策略,最直接的做法就是关闭。本篇区分临时关闭和永久关闭,并给出验证方法。

一、先查看SELinux当前状态

# 查看运行状态
getenforce
# 返回 Enforcing 表示强制开启,Permissive 只记录不拦截,Disabled 已关闭
sestatus
# 也可看配置文件
grep SELINUX= /etc/selinux/config

二、临时关闭(重启后恢复)

# 切换为宽容模式,立即生效但重启复原
setenforce 0
# 验证
getenforce
# 返回 Permissive 即临时关闭成功

setenforce 1 可重新切回 Enforcing。临时关闭适合先定位问题,不想动配置时使用。

三、永久关闭(重启仍生效)

编辑配置文件:

vi /etc/selinux/config

把这一行:

SELINUX=enforcing

改为:

SELINUX=disabled

保存后必须重启服务器才完全生效:

reboot

重启后执行 getenforce 返回 Disabled 即永久关闭成功。

四、两种方式区别速记

方式 命令/改文件 是否立即生效 重启后
临时 setenforce 0 是 恢复为Enforcing
永久 /etc/selinux/config 改disabled 否,需重启 保持关闭

五、常见坑

  1. 只 setenforce 0 不重启改文件:重启后又自动开启,问题复现;
  2. 改了配置不重启:getenforce 仍显示 Enforcing,以为没改成功;
  3. 改错变量名:是 SELINUX= 而不是 SELINUXTYPE=,后者是策略类型别动;
  4. 急着排查时建议先临时关:验证是不是SELinux拦截,确认后再决定永久关,避免一刀切损失安全性;
  5. 某些面板/容器环境改完仍受影响,可用 sestatus 复核当前模式。

总结

临时关用 setenforce 0,永久关改 /etc/selinux/config 为 disabled 后重启,最后用 getenforce 确认。排查403、写入失败时,SELinux是经常被忽略的一层。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-20 07:25 

Linux查看服务器开放端口,netstat和ss命令教程

前言

排查网站打不开、端口是否被占用、服务有没有监听,都要先看服务器开放了哪些端口。Linux 上最常用的两个工具是 netstat 和 ss,ss 是新系统推荐的替代,速度更快。本篇讲清楚两个命令的常用组合和怎么判断监听状态。

一、ss命令(推荐,CentOS7自带)

# 查看所有正在监听的TCP端口
ss -tlnp
# 查看所有正在监听的UDP端口
ss -ulnp
# 统计各类连接数量
ss -s

参数含义:-t TCP、-u UDP、-l 只看监听、-n 数字形式显示端口号(不解析域名/服务名)、-p 显示占用进程。

二、netstat命令(老系统常用,需net-tools)

CentOS7 最小化可能没装,先装:

yum install -y net-tools

常用:

# 查看所有监听的TCP端口和进程
netstat -tlnp
# 查看UDP监听
netstat -ulnp
# 查看80端口被谁占用
netstat -tlnp | grep ':80'
# 查看所有已建立连接
netstat -anp | grep ESTABLISHED

三、判断端口是否在监听

以80端口为例:

ss -tlnp | grep ':80'
# 或
netstat -tlnp | grep ':80'
  • LISTEN 表示正在监听,服务正常;
  • 没有任何输出说明该端口没被程序监听;
  • 最后一列能看到进程名和PID,比如 nginx: worker。

四、常用排查组合

# 查某个具体端口(443)
ss -tlnp | grep 443
# 查某进程监听了哪些端口
ss -tlnp | grep nginx
# 实时看连接数最多的IP
ss -tn state established | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head

五、常见坑

  1. 看不到进程名要加 -p,且需要root:普通用户只能看到端口,看不到占用程序,用 sudo 或 root 执行;
  2. 只看监听不够:服务在本机监听但防火墙/安全组没放行,外网依然连不上,还要配合 firewalld 和云安全组检查;
  3. netstat 找不到命令:是没装 net-tools,yum install -y net-tools;
  4. -n 别漏:不加会把80显示成http,grep端口号时容易匹配不到。

总结

记住 ss -tlnp 看 TCP 监听、ss -ulnp 看 UDP、| grep 端口号 精准定位,老机器用 netstat 同参数即可。先确认本机监听,再查防火墙和安全组,排查链路才完整。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-20 07:24 

CentOS7 firewalld开放80、443端口永久生效命令

前言

CentOS7 默认用 firewalld 管理防火墙,新装系统后外网访问 80、443 经常被拒,浏览器打不开网站。很多人只在临时会话里加规则,重启或 reload 后端口又被关。本篇给出永久开放 80、443 的完整命令和验证方法。

一、先确认防火墙状态

systemctl status firewalld
firewall-cmd --state

返回 running 表示防火墙在运行;如果是 not running,先启动并设为开机自启:

systemctl start firewalld
systemctl enable firewalld

二、永久开放80、443端口

关键是 --permanent 参数,不加它规则只在当前运行时生效,reload 或重启就丢失。

# 永久开放HTTP 80
firewall-cmd --permanent --add-port=80/tcp
# 永久开放HTTPS 443
firewall-cmd --permanent --add-port=443/tcp
# 重载使永久规则立即生效
firewall-cmd --reload

也可以用服务名方式(firewalld 自带 http/https 服务定义):

firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

三、查看已开放端口,确认生效

# 查看永久规则里已开放的端口
firewall-cmd --permanent --list-ports
# 查看当前运行时生效的服务
firewall-cmd --list-services

能看到 80/tcp 443/tcp 或 http https 即成功。

四、开放单个/多个端口常用命令汇总

# 永久开放单个端口
firewall-cmd --permanent --add-port=8080/tcp
# 永久移除某个端口
firewall-cmd --permanent --remove-port=8080/tcp
# 开放一段端口范围(如30000-30100)
firewall-cmd --permanent --add-port=30000-30100/tcp
# 开放UDP(DNS、视频流用)
firewall-cmd --permanent --add-port=53/udp

每加完一条都要 firewall-cmd --reload。

五、常见坑

  1. 加了规则但不 reload:运行时看不到,外网照样访问不通;
  2. 忘记 --permanent:重启服务器后规则消失,以为没生效;
  3. 协议选错:Web 是 tcp,写成 udp 无效;
  4. 云服务器安全组没放行:系统防火墙开了,云厂商控制台安全组还拦着,两边都要开;
  5. 服务本身没监听:ss -tlnp 确认 nginx 在 80/443 上监听,否则放了端口也连不上。

总结

记住三步:--add-port=端口/tcp --permanent → --reload → --list-ports 验证。系统防火墙和云安全组同时放行,网站才能从外网访问。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-20 07:22 

Linux查看登录用户

Linux查看登录用户

引言

服务器被多人SSH登录或排查异常连接时,需要看清当前是谁在线、从哪里连进来、在做什么。who、w、id、last几个命令配合,能区分当前身份、在线会话和历史登录记录。

我是谁:whoami

whoami

直接输出当前执行命令的用户名,脚本里判断身份最常用。

查看自己的UID和组

id

输出uid、gid和所属附加组,比whoami信息更全,判断权限时用。

谁在线:who

who

列出所有已登录用户、终端tty、登录时间和来源IP。

在线会话详情:w

w

比who多一列当前正在执行的命令和空闲时长,能看到每个会话在干什么。

历史登录记录:last

last -10

读取/var/log/wtmp,列出最近登录的用户、终端、来源IP和时长,排查入侵看历史。

查特定用户

w root
last root

指定用户名过滤,聚焦某账号的在线和历史。

注意事项

  • whoami只看当前身份,who/w看所有在线会话;
  • w的JCPU是终端累计时间,PCPU是当前进程时间;
  • last依赖wtmp日志,日志轮转后只保留近期;
  • 异常来源IP、陌生账号要结合last和/var/log/secure排查;
  • 系统服务进程不占登录会话,who看不到;
  • 切换用户su后whoami变新用户,但w仍显示原始登录终端。

总结

whoami看当前用户名,id看UID和组,who列在线用户与来源IP,w多显示正在执行的命令,last查历史登录记录。排查时先w看当前在线会话,再last回溯历史,陌生IP和账号结合secure日志,su切换后身份以whoami为准但终端归属原始登录人。


emer 发布于  2026-10-20 07:21 

Redis设置过期时间

Redis设置过期时间

引言

缓存键必须带TTL自动失效,否则热点数据永久驻留直到内存写满。EXPIRE设置秒级过期,PEXPIRE毫秒,SET组合NX/EX可一步完成写入带过期,是防止缓存永久堆积的基本功。

写入时直接带过期

SET code:1001 123456 EX 300 NX

EX 300表示5分钟后过期,NX只在键不存在时写入,一次命令完成防重复与失效。

给已有键补过期

EXPIRE code:1001 300

键存在返回1,键不存在返回0,只设置剩余存活秒数。

查看剩余存活

TTL code:1001

返回剩余秒数;-1表示永久不过期,-2表示键已不存在。

毫秒级过期

PEXPIRE code:1001 300000
PTTL code:1001

对验证码、限流等需要毫秒精度的场景使用。

取消过期变永久

PERSIST code:1001

移除TTL让键永久保留,需要长期数据时用。

绝对值过期

EXPIREAT code:1001 1760000000

以Unix时间戳设定具体到期时刻,适合定时失效场景。

注意事项

  • EXPIRE针对键整体,hash内部field不单独过期;
  • 覆盖写值(SET同名键)会清掉旧TTL,需重新设置;
  • TTL是相对当前时间,持久化恢复后仍按剩余时间走;
  • 分布式锁用EXPIRE要配合SET NX原子写入,别先SET再EXPIRE;
  • 大量键同时过期要错开时间,避免雪崩;
  • TTL返回-1和-2含义不同,排查时别混。

总结

SET加EX一步写入带秒级TTL,EXPIRE给已有键补过期,TTL查剩余秒数,PEXPIRE/PTTL用毫秒,PERSIST取消过期变永久,EXPIREAT按时间戳定点失效。注意覆盖写会清TTL需重设,hash字段无单独过期,分布式锁要原子NX加过期,批量过期错开避免雪崩。


emer 发布于  2026-10-20 07:20 

PHP获取客户端IP

PHP获取客户端IP

引言

记录访问日志、做地域统计或IP限定时要拿真实访客IP。经过Nginx反代或CDN后,REMOTE_ADDR是代理地址,真实IP藏在转发头里,直接取单一字段容易拿到代理IP,需要按可信链兜底并校验。

直连场景

$ip = $_SERVER['REMOTE_ADDR'] ?? '';

没有反代时,这就是直连客户端IP。

反代后取真实IP

$headers = [
    'HTTP_X_FORWARDED_FOR',
    'HTTP_X_REAL_IP',
    'HTTP_CLIENT_IP',
];
foreach ($headers as $h) {
    if (!empty($_SERVER[$h])) {
        $ip = explode(',', $_SERVER[$h])[0];
        break;
    }
}

X-Forwarded-For可能是逗号分隔的IP链,取第一个才是原始客户端。

封装函数

function getClientIp(): string {
    foreach (['HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP'] as $h) {
        if (!empty($_SERVER[$h])) {
            $ip = trim(explode(',', $_SERVER[$h])[0]);
            if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip;
        }
    }
    return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

filter_var校验格式,非法就回退到REMOTE_ADDR。

代理头必须由服务端写入

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

头要由可信Nginx设置,不能完全信任客户端自带。

注意事项

  • XFF是逗号链,取最左原始IP;
  • 客户端可伪造XFF,只信任自己配置的代理;
  • filter_var校验IPv4/IPv6格式;
  • 不要用伪造IP做安全鉴权,登录风控仍需服务端;
  • CDN场景真实IP在CDN回源头,需查厂商文档;
  • 内网代理REMOTE_ADDR是网关IP,别当访客IP。

总结

直连取REMOTE_ADDR,反代后从X-ForwardedFor、X-Real-IP按序兜底,XFF逗号链取第一个,filter_var校验后回退,代理头由可信Nginx写入。关键是客户端可伪造转发头,只信任自己配置的代理链,IP适合统计不适合单独做安全判定,CDN需用厂商回源头。


emer 发布于  2026-10-20 07:19 

Nginx绑定域名配置

Nginx绑定域名配置

引言

一台服务器跑多个站点,靠server_name区分访问哪个域名,配合DNS解析和站点根目录,Nginx按Host头把请求路由到对应虚拟主机。配置顺序写错或server_name不匹配,会落到默认站点。

基础域名站点

server {
    listen 80;
    server_name www.example.com example.com;
    root /var/www/example;
    index index.html index.php;
}

server_name可并列写主域名和裸域名,root指向站点目录。

裸域名跳转到www

server {
    listen 80;
    server_name example.com;
    return 301 https://www.example.com$request_uri;
}

避免两个域名都解析内容造成重复收录,统一到一个规范域名。

多个域名并列

server_name api.example.com admin.example.com;

同一套站点对应多个子域名时直接空格分隔。

反向代理绑定域名

server {
    listen 80;
    server_name app.example.com;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
    }
}

域名指向后端服务,记得把Host头透传给上游。

配置后检查重载

nginx -t
nginx -s reload

先测语法再平滑重载,reload不断连接。

匹配规则

  • 精确域名优先,然后通配符,最后默认站点;
  • 没匹配上server_name的请求落到default_server;
  • 通配符*.example.com不能匹配example.com裸域;
  • 多站点注意别让无关域名被默认站点接住。

注意事项

  • DNS要先把A记录解析到服务器IP,否则域名打不开;
  • server_name区分大小写但通常小写;
  • 80改443需另配SSL证书块;
  • return 301统一域名利于SEO避免重复;
  • 防火墙和安全组放行80、443;
  • 多个server块listen 80,靠server_name分流。

总结

server_name在server块绑定域名,可并列多个,裸域301跳www统一规范,反代时透传Host,改完nginx -t检查再reload。匹配精确优先于通配符,未命中走默认站点,前提是DNS解析到位、端口放行,统一域名有助于SEO避免重复收录。


emer 发布于  2026-10-20 07:18 

Git查看远程分支

Git查看远程分支

引言

团队协作时,远程仓库可能已经开了新分支或删了旧分支,本地缓存的远程引用不会自动更新。先fetch同步远端信息,再用-r参数列出远程分支,避免基于过期列表误操作。

列出所有远程分支

git branch -r

列出remote-tracking分支,origin/前缀代表远端,注意这是本地缓存的远端快照。

先同步再查看

git fetch origin
git branch -r

fetch拉取最新远端引用后再列,才能看到别人刚推上来的新分支。

本地加-r看全部

git branch -a

-a同时列本地分支和远程跟踪分支,方便对比两边差异。

查看远程分支最新提交

git log origin/main --oneline -5

不切分支也能看远程分支最近几条提交历史。

查看远程仓库地址

git remote -v

列出fetch和push两个地址,确认连接的是正确远端。

清理已删除的远程分支缓存

git fetch --prune origin

远端已删的分支,本地列表里还留着,prune清掉过期引用。

按关键词筛选

git branch -r | grep feature

分支多时用grep过滤feature、release等前缀。

注意事项

  • git branch -r看的是本地缓存,先fetch再看才准;
  • 远程分支不能直接checkout修改,要建本地跟踪分支;
  • origin/main是跟踪引用,不是独立可写分支;
  • prune清理远端已删分支的本地残留;
  • 切远程分支用git checkout -b 本地名 origin/远程名;
  • 多远端要指定remote名,默认origin。

总结

git branch -r列出本地缓存的远程跟踪分支,先git fetch同步最新,-a同时看本地远程,log看远程分支提交,remote -v看仓库地址,fetch --prune清理过期引用,grep筛选前缀。核心是列表是缓存快照,操作前先fetch,远程分支只读,改动需建本地跟踪分支再推送。


emer 发布于  2026-10-20 07:17 

MySQL修改字段类型

MySQL修改字段类型

引言

上线后字段长度不够、类型选错是常见需求,ALTER TABLE MODIFY可以改字段类型,但收缩长度或跨类型可能丢数据,大表执行还会长时间锁表,动手前必须备份和评估。

修改字段类型与长度

ALTER TABLE users
MODIFY COLUMN phone VARCHAR(20) NOT NULL DEFAULT '';

MODIFY重写该列完整定义,要把NOT NULL、默认值、字符集一起带上,否则会被重置。

修改字段名:CHANGE

ALTER TABLE users
CHANGE COLUMN tel phone VARCHAR(20) NOT NULL;

CHANGE既要写旧名也要写新名,顺便改类型,MODIFY只改类型不改名。

数字类型扩容

ALTER TABLE orders
MODIFY COLUMN amount DECIMAL(12,2) NOT NULL;

金额从DECIMAL(10,2)扩到(12,2)属于安全扩容,精度扩大不丢数据。

字符串加长

ALTER TABLE posts
MODIFY COLUMN title VARCHAR(255) NOT NULL;

varchar加长一般安全,注意字符集下长度是字符数不是字节。

大表谨慎执行

# 大表避开高峰,用pt-online-schema-change或gh-ost在线改

直接ALTER在InnoDB可能重建整表并锁写,百万行以上务必在线工具。

改前必查

SELECT COUNT(*) FROM users WHERE CHAR_LENGTH(phone) > 20;

先查有没有超长数据,收缩长度前先清理,否则直接截断或报错。

注意事项

  • MODIFY要带全字段约束,别只写类型;
  • CHANGE改名+改类型,MODIFY只改类型;
  • 收缩长度、改窄类型可能截断丢数据,先备份;
  • 大表用在线DDL工具,避开高峰;
  • 改完用SHOW CREATE TABLE核对;
  • 类型转换不兼容(如字符串转数字)会失败或变0,先清洗数据。

总结

MODIFY改字段类型需重写完整定义含约束默认值,CHANGE可同时改名,数字扩容和字符串加长相对安全,收缩长度跨类型前先查超长数据并备份,大表用pt-osc或gh-ost在线改避免锁表,改完SHOW CREATE核对。生产改表先备份再评估锁表风险。


emer 发布于  2026-10-20 07:16 

PHP空合并运算符

PHP空合并运算符

引言

从请求数组里取值时,直接访问不存在的键会触发Notice报错。isset加三元判断写法啰嗦,PHP7引入的??空合并运算符一行搞定,且不存在键时不报Notice,是接口参数处理的高频写法。

基本用法

$page = $_GET['page'] ?? 1;

键存在且非null就取它的值,否则取默认值1,全程不报Notice。

对比老式写法

// 旧写法,啰嗦且易漏isset
$page = isset($_GET['page']) ? $_GET['page'] : 1;

// 新写法,等价且简洁
$page = $_GET['page'] ?? 1;

两段逻辑完全等价,新写法可读性明显更好。

链式兜底

$name = $input['name'] ?? $request['name'] ?? '匿名用户';

依次尝试,第一个非null的值被采用,最后一个兜底默认值。

数组嵌套取值

$city = $data['user']['city'] ?? '未知城市';

中间任何一层键缺失都安全返回默认值,不会因层级不存在报错。

??与||的区别

// ??只判断null和未定义,空字符串0都保留
$q = $_GET['q'] ?? '';

// ||会把0、空字符串当假,业务语义不同

注意0和空字符串不是null,??会原样保留。

赋值场景:合并赋值

$config['debug'] ??= false;

??=只在键不存在或为null时赋值,已存在则不动。

注意事项

  • ??只在未定义或null时兜底,空串、0、false不算空;
  • 数组深层取值安全,但类型仍要自行校验;
  • 不要用??替代输入过滤,默认值之外仍需验证;
  • PHP5不支持??,老环境需改用isset三元;
  • 链式写法注意短路,前面命中即返回;
  • 业务关键参数兜底后仍要做合法性范围检查。

总结

??判断键是否存在或为null,缺省返回默认值且不报Notice,替代isset三元,支持链式兜底和数组深层取值,??=实现条件赋值。区别于||,0和空字符串会被保留不会被兜底,PHP5需用旧写法,取到默认值后关键参数仍要校验合法性。


emer 发布于  2026-10-20 07:15 

Linux查看目录占用

Linux查看目录占用

引言

磁盘满了先定位是哪个目录吃掉空间,df看整体分区,du逐层下钻找大户。两个命令配合,从分区到目录到文件逐级缩小范围,是磁盘排查的标准路径。

看整体分区:df

df -h

人类可读格式显示各分区总量、已用、可用、使用率,先判断是哪个挂载点满了。

看单个目录总大小

du -sh /var/log

-s汇总,-h人类可读,直接给出这个目录总共占多大。

看一级子目录大小并排序

du -h --max-depth=1 /var | sort -rh | head

只算一层子目录,sort -rh按人类可读大小倒序,head取前几名,快速定位大户。

常用简写

du -sh /var/*

列出/var下每个一级目录大小,逐层进入继续排查。

排除文件系统统计

du -shx /home

-x不跨文件系统,避免把挂载的别的盘也算进来。

看目录下文件数量

ls /var/log | wc -l

有些目录不大但文件海量,inode耗尽也会写不进,配合df -i看inode。

注意事项

  • df看分区,du看目录,先df定位再du下钻;
  • 大目录du会遍历较慢,加--max-depth限制层数;
  • 删除大文件后空间没释放,可能是进程仍持有句柄,需重启进程;
  • sort -rh依赖du输出的G/M单位;
  • /proc、/sys是虚拟文件系统,du统计无意义;
  • 日志目录优先排查,常是增长最快的大户。

总结

df -h看分区整体,du -sh看单目录汇总,--max-depth=1配合sort -rh找一级大户,-x不跨文件系统,ls加wc -l看文件数,df -i查inode。排查路径是先df定位满的分区,再du逐层下钻,删文件后空间不释放要查持有句柄的进程,日志目录优先检查。


emer 发布于  2026-10-20 07:14 

Redis查看键数量

Redis查看键数量

引言

排查内存占用、统计缓存规模、清理过期数据前,先要看清当前库有多少键、某前缀下有多少键。不同命令代价差别很大,生产环境绝对不能用KEYS全量扫描,否则会阻塞Redis。

统计当前库总键数:DBSIZE

DBSIZE

返回当前数据库的键总数,是O(1)操作,底层直接读计数器,生产环境可安全使用。

切换库再统计

SELECT 2
DBSIZE

Redis默认0到15共16个逻辑库,DBSIZE只统计当前选中的库。

统计指定前缀数量:SCAN配合

SCAN 0 MATCH user:* COUNT 100

SCAN游标式分批遍历,不会阻塞主线程,配合MATCH按前缀过滤,COUNT是每批建议数量而非精确数量。

错误做法:KEYS全扫

# 生产禁止
KEYS user:*

KEYS是O(n)一次性遍历全部键,键多时会长时间阻塞,线上直接用可能引发雪崩。

估算内存与键分布

INFO keyspace

一次性返回所有库的键数量、过期键比例、平均TTL,适合整体巡检。

按类型统计

SCAN 0 TYPE string COUNT 500

SCAN支持按数据类型过滤,筛选string、hash、list等。

脚本批量统计前缀

redis-cli --scan --pattern 'order:*' | wc -l

命令行用--scan流式遍历,管道交给wc计数,比KEYS安全。

注意事项

  • DBSIZE是O(1)放心用,KEYS是O(n)生产禁;
  • SCAN结果可能重复,业务去重;
  • DBSIZE不含已过期未清理键,近似看规模;
  • 多库要逐个SELECT统计;
  • 大库统计用INFO keyspace代替自己遍历;
  • 删前缀键也用SCAN加DEL,别配KEYS。

总结

DBSIZE O(1)看当前库总数,SELECT切库分别统计,指定前缀用SCAN游标遍历,生产禁用KEYS全扫,INFO keyspace看全库分布和TTL,命令行用--scan加wc计数。核心是别用阻塞命令,SCAN分批处理允许重复需去重,多库分别统计,清理键同样走SCAN。


emer 发布于  2026-10-20 07:13 

MySQL创建数据库

MySQL创建数据库

引言

新项目第一步往往是建库,CREATE DATABASE语法简单,但字符集和排序规则选错会在后期引发中文乱码、表情存不下、排序不一致等麻烦,最好在建库时一次定好。

基本建库

CREATE DATABASE mydb;

用默认字符集建库,生产环境不建议这样,最好显式指定。

指定字符集建库(推荐)

CREATE DATABASE mydb
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;

utf8mb4支持完整Unicode和emoji,general_ci不区分大小写,绝大多数Web项目够用。

判断不存在再建

CREATE DATABASE IF NOT EXISTS mydb
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

IF NOT EXISTS防止重复执行报错,初始化脚本常用。

查看建库语句

SHOW CREATE DATABASE mydb;

确认字符集、排序规则和预期一致。

修改已有库字符集

ALTER DATABASE mydb
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;

只改库默认值,已有表和列的字符集不会跟着变,需逐表处理。

删除数据库

DROP DATABASE IF EXISTS mydb;

不可逆,删前必须备份,IF EXISTS避免报错中断脚本。

授权配合

CREATE USER 'appuser'@'localhost' IDENTIFIED BY '强密码';
GRANT ALL ON mydb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;

建库后给专用账号授权,不用root跑业务。

注意事项

  • 统一utf8mb4,别用过时的utf8(只占3字节);
  • 排序规则ci结尾不区分大小写,bin区分;
  • 库名小写、下划线分隔,避免跨平台大小写坑;
  • 业务账号最小权限,禁止给ALL on .;
  • ALTER库字符集不改存量表,需单独刷表;
  • 初始化脚本加IF NOT EXISTS保证可重复执行。

总结

CREATE DATABASE建库,生产显式指定utf8mb4和排序规则,IF NOT EXISTS让脚本可重复执行,SHOW CREATE核对,ALTER只改默认值,DROP不可逆先备份,配合专用账号最小授权。建库定好字符集能避免后期乱码,库名规范小写,权限按库授予,别用root直接跑业务。


emer 发布于  2026-10-20 07:12 

PHP去除字符串空格

PHP去除字符串空格

引言

用户输入、表单提交常带首尾空格甚至全角空格、换行,入库前不清洗会导致登录比对失败、查询匹配不上、统计失真。trim系列处理首尾,preg_replace处理中间多余空格,按需选用。

去除首尾空白:trim

$name = trim("  张三  ");
// 结果:张三,首尾空格、制表符、换行一并去掉

trim默认去空格、tab、换行、回车、null字节等首尾空白。

只去左侧:ltrim

$path = ltrim("/var/www/", "/");
// 结果:var/www/,第二个参数指定要剥掉的字符集

只去右侧:rtrim

$file = rtrim("photo.jpg/", "/");
// 结果:photo.jpg/,只剥右边指定字符

自定义去除字符

trim($str, " \t\n,");

第二个参数列出要去除的字符,不止空格,可同时去逗号、换行。

去掉中间多余空格

$str = preg_replace('/\s+/', ' ', $str);
// 多个连续空白压成一个空格
$str = preg_replace('/\s/', '', $str);
// 彻底删除所有空格和空白

trim管首尾,中间空格必须用正则。

处理全角空格

$str = str_replace(' ', '', $str);
// 全角空格\u3000普通trim去不掉,要单独替换

数组批量清洗

$clean = array_map('trim', $_POST);

对整个表单数组逐个trim,登录注册入口常用。

注意事项

  • trim只处理首尾,别指望它删中间空格;
  • 全角空格普通trim无效,需str_replace单独处理;
  • 密码字段不要trim,避免误改用户输入;
  • 第二个参数是字符集不是整串,按字符逐个剥;
  • 入库前统一清洗,查询和比对才一致;
  • 中文人名中间正常空格要保留,只压连续空白。

总结

trim去首尾空白,ltrim去左,rtrim去右,第二个参数自定义字符集,中间连续空格用preg_replace压成一个或全删,全角空格单独str_replace,表单用array_map批量清洗。记住trim只管首尾,密码不清洗,全角空格要单独处理,入库前统一规范保证比对一致。


emer 发布于  2026-10-20 07:11 

Nginx监听端口配置

Nginx监听端口配置

引言

listen是server块的第一行指令,决定Nginx在哪个端口接收请求。一个server监听一个端口加域名组合,多站点靠server_name区分,多端口靠listen叠加。配错端口是最常见的502和无法访问根源。

基础HTTP监听

server {
    listen 80;
    server_name example.com;
    root /var/www/site;
    index index.html;
}

监听80端口,匹配example.com域名的请求进这个站点。

HTTPS监听

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;
}

443必须加ssl关键字,同时配证书和私钥路径。

监听多个端口

server {
    listen 80;
    listen 8080;
    server_name example.com;
}

一个server块写多行listen,80和8080都走同一套配置。

只监听指定IP

listen 127.0.0.1:8080;

只绑回环地址,外部访问不到,适合内部管理接口。

80跳转到443

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

单独一个80 server做永久跳转,把HTTP请求引导到HTTPS。

默认服务器

server {
    listen 80 default_server;
}

default_server指定未匹配域名时的兜底站点,防直接用IP访问暴露默认页。

注意事项

  • 端口冲突先ss -tlnp查占用,别盲目改;
  • 443漏写ssl会导致证书不生效;
  • 云服务器还要在安全组放行对应端口;
  • listen不带IP默认绑0.0.0.0全部网卡;
  • 改端口后先nginx -t再reload;
  • 301跳转的旧URL参数用$request_uri保留。

总结

listen 80配HTTP,listen 443 ssl配HTTPS并带证书,多行listen监听多端口,IP:端口限定绑定网卡,80 server用return 301跳HTTPS,default_server做域名兜底。改前ss查占用、nginx -t校验、安全组放行,绑定IP时注意内外网可达性,跳转用$host和$request_uri保住完整地址。


emer 发布于  2026-10-20 07:10