How to check if your website or server is under attack
Four checks, in order: the site from outside, the access log, open connections and the SSH auth log. The commands, and how to tell an attack from a traffic spike.
Sandeep Sidhu · Founder, AlertKick
The site is slow and throwing errors, and you need to know in the next ten minutes whether it is an attack, a traffic spike, or a bug.
Check four places, in this order: the site from outside, the web access log, open connections, and the SSH auth log. An attack shows up as concentration: a few IPs, a few paths, or one login endpoint taking a share of traffic that real users never produce. A real traffic spike is spread across many IPs, normal pages, and normal referrers.
This is for an attack in progress. If the attack may have succeeded, use the post-compromise checklist instead.
Is it down for everyone, or just you?
Before digging into logs or connections, check the site from outside the server. The free website check shows the HTTP status, the redirect chain, and timing per phase: DNS, TCP connect, TLS handshake, and first byte.
The slow phase points to the bottleneck. A slow TCP connect or a timeout indicates a network-level issue, such as a connection flood. A fast TCP connect paired with a slow first byte means the network is fine, but the application is overloaded. This pattern often appears during a request flood against an expensive page that ties up backend resources.
If the check shows the site is unreachable from the outside, the problem is likely volumetric or network-layer. If the site loads but is slow, the issue is application-layer.
If the site is up but behaving strangely, move on to the access log.
What do the access logs show?
The web access log is the primary record of what the server received. Default paths vary by stack: nginx uses /var/log/nginx/access.log, Apache on Debian/Ubuntu uses /var/log/apache2/access.log, and Apache on RHEL family uses /var/log/httpd/access_log. The commands below assume the default combined log format.
Start by identifying the top client IPs. A small number of IPs generating a large share of requests often indicates an attack, whereas a real traffic spike spreads across many sources.
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
Check for sudden spikes in request volume by looking at requests per minute.
awk '{print substr($4, 2, 17)}' /var/log/nginx/access.log | uniq -c | tail -30
Identify which paths are being targeted. Attacks often hammer a single URL, a login endpoint, or probe for common weak spots like WordPress login, XML-RPC, .env, .git, or phpMyAdmin.
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
Scan for these specific probes to identify scanners regardless of other traffic patterns.
grep -E 'wp-login\.php|xmlrpc\.php|/\.env|/\.git/|phpmyadmin' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head
Analyze the status code mix. A flood of 404s suggests scanning. A flood of 401 or 403 responses on a login path indicates credential guessing. A rise in 499, 502, or 504 errors means the backend is struggling under load.
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
Check user agents. Real traffic shows a normal browser mix. Empty, scripted, or a single agent dominating the list is a strong signal of automated abuse.
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
If the site sits behind a CDN or load balancer, the first field in the log is the proxy’s IP, not the client. The real client IP appears in the X-Forwarded-For header (or CF-Connecting-IP on Cloudflare), but only if the log format is configured to record it.
What are the connections on the box?
The access log shows what already happened. The kernel’s connection table shows what is happening now.
Start with the summary of socket counts by protocol and state.
ss -s
Look for half-open connections. A high and growing number of these is the signature of a SYN flood. The attacker sends connection requests but never completes the handshake, leaving the server waiting.
ss -tan state syn-recv | wc -l
If the site is on port 443, check which remote IPs hold established connections. This shows where the actual load is coming from.
ss -tn state established '( sport = :443 )' | awk 'NR>1 {print $4}' | sed 's/:[0-9]*$//' | sort | uniq -c | sort -rn | head
Under SYN flood pressure, the kernel starts sending SYN cookies to keep accepting connections without consuming memory for half-open states. Check if this protection is active.
nstat -az | grep -i syncookies
Finally, compare load average to CPU count. A load average above the number of cores (nproc) for a sustained period means the box is saturated. There is no universal “safe” load number.
Is SSH under attack?
SSH brute force is the most common attack on any internet-facing Linux server. It runs whether or not the website is the target.
Check failed password attempts per source IP:
grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head
Use /var/log/secure on RHEL, Alma, or Rocky.
Hosts with no auth.log file (journald only):
journalctl -u ssh --since "1 hour ago" | grep -c "Failed password"
The unit is ssh on Debian/Ubuntu and sshd on RHEL family.
Most important, check whether any login succeeded during the attack window.
grep "Accepted" /var/log/auth.log | tail -20
A successful login from an IP that was also failing is the moment to stop diagnosing and switch to the post-compromise checklist.
The free auth.log analyzer does this breakdown in the browser (failed attempts per IP, usernames tried, successful logins during the attack window, threat-feed reputation for the top sources). The log never leaves the browser.
Who are the top IPs?
Once the logs or connection tables give you suspicious IPs, find out what they are. Reverse DNS is the quick first hint.
dig -x 203.0.113.7 +short
This command returns the hostname associated with the IP address. It usually tells a residential ISP apart from a cloud provider or hosting company.
The free IP reputation checker checks an IP against three live threat feeds (Spamhaus DROP, IPsum, URLhaus) with reverse DNS and a plain verdict.
Before blocking a high-volume IP, check it is not a search crawler. Google’s crawler IPs reverse-resolve to googlebot.com or google.com, and the forward lookup of that name returns the same IP. Blocking a legitimate crawler can hurt search visibility without stopping an attack.
Is it an attack or a traffic spike?
The difference is concentration. The access log commands above give the raw numbers; read them against this table:
| Signal | Real traffic spike | Attack |
|---|---|---|
| Source IPs | Many, spread out | A few IPs or one network carry a large share |
| Paths | Normal pages, a launch or a linked article | One URL hammered, login endpoints, probe paths |
| Status codes | Mostly 200 | Spikes of 404, 401/403, or 5xx |
| User agents | Normal browser mix | Empty, scripted, or one agent dominating |
| Referrers | A link, a newsletter, a social post | None |
If a single IP or a small subnet is hammering one endpoint, it is an attack: block and rate limit, as below. If the traffic is spread across many IPs and normal pages, it is a real spike, and the answer is more capacity.
What to do right now
Write down timestamps and top IPs before changing anything. It helps afterwards.
Block a single abusive IP:
sudo ufw deny from 203.0.113.7
Or with iptables:
sudo iptables -I INPUT -s 203.0.113.7 -j DROP
Rate limit per client IP in nginx. In the http block:
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
In the location block:
limit_req zone=perip burst=20 nodelay;
Then test and reload:
sudo nginx -t && sudo systemctl reload nginx
Secure SSH. Set PasswordAuthentication no in sshd_config to end password brute force as a risk. fail2ban bans IPs after repeated failures. The Linux server security checklist covers the rest of the hardening.
If the network link itself is full, the attack is volumetric. No server-side command fixes this. The signal is that the website check times out at TCP connect while the server shows little load. Contact the hosting provider or enable a CDN/DDoS protection service.
If anything got in, switch to the post-compromise checklist.
How to catch the next one without watching logs
Every check above needs someone who is already suspicious. Attacks run at 3 AM and the log scrolls past. Closing that gap takes detection that runs all the time and pages a human.
The AlertKick agent follows the SSH auth log and falls back to the systemd journal on hosts with no auth.log file. It raises an SSH brute-force finding when one source IP fails 5 times within 120 seconds. SSH login monitoring records every login with the user, source IP, and session length. Logins from untrusted IPs page the on-call engineer. More on SSH login monitoring, and the DIY ways to get login notifications if you want to build it yourself.
External HTTPS monitors check the site from outside, and response-time alerting fires when a site stays slow while still returning 200.
Alerts go through escalation policies and on-call rosters to Slack, email, SMS and mobile push.
External monitors and on-call are on the free plan; the agent with SSH detection starts on Professional (5 servers, £39 a month). Start free.
Frequently asked questions
- How do I tell if my server is under attack or just experiencing a traffic spike?
- An attack shows concentration: a few IPs, a few paths, or one login endpoint taking a large share of traffic. A real traffic spike is spread across many IPs, normal pages, and normal referrers. Look for spikes in 404, 401/403, or 5xx status codes, which indicate scanning or credential guessing rather than legitimate user interest.
- What does a slow TCP connect versus a slow first byte mean?
- A slow TCP connect or timeout points to a network-level issue, such as a connection flood saturating the link. A fast TCP connect with a slow first byte means the network is fine but the application is overloaded. This pattern often appears during a request flood against an expensive page that ties up backend resources.
- How can I check if SSH brute force attacks are happening on my server?
- Check failed password attempts per source IP using grep on /var/log/auth.log or /var/log/secure. On hosts using journald, use journalctl to count failed password entries. The most critical check is to look for any successful logins from IPs that were also failing, which indicates a breach.
- What should I do if the website check times out at TCP connect but the server load is low?
- This signal indicates a volumetric DDoS where the network link itself is full. The server shows little load because it never receives the packets. This cannot be fixed on the server; it requires the hosting provider or a CDN/DDoS protection service in front of the site.
- How do I verify if a high-volume IP is a legitimate search crawler?
- Run a reverse DNS lookup on the IP to see if it resolves to googlebot.com or google.com. Then perform a forward lookup on that name to ensure it returns the same IP. Blocking a legitimate crawler can hurt search visibility without stopping an attack.
- What is the first step to take when blocking an abusive IP?
- Write down timestamps and top IPs before changing anything to help with post-incident analysis. Then use ufw or iptables to deny traffic from the specific IP address. For nginx, you can also add rate limiting rules to the http or location blocks to restrict requests per client IP.