grep prints the lines that match a regular expression. Basic syntax (the default) treats +, ?, | and ( as literals; -E makes them operators; -P adds Perl features such as \d and \K, which drops what was matched so far from the output; -F matches a fixed string. Single-quote every pattern, and escape the dots in an IP address, since a bare . matches any character.
sudo cp /var/log/apache2/access.log . && sudo chown "$USER": access.log
wc -l access.log
grep -c '" 404 ' access.log # Not Found responses
grep -ciE 'bot|spider|crawl' access.log # crawler requests, any case
grep -m 2 -E '"GET /(wp-login|xmlrpc)\.php' access.log | cut -d' ' -f1,4,7,9
grep -oP '"GET \K/\.[^ ]+' access.log | sort -u # probes for dot-files517 access.log 111 113 203.0.113.99 [23/Sep/2026:13:53:37 /xmlrpc.php 404 203.0.113.99 [23/Sep/2026:13:53:37 /wp-login.php 404 /.env /.git/config
'" 404 ' pins the status between the request's closing quote and the byte count, so a URL containing 404 is not counted. Requests for /.env and /.git/config are scanners hunting for leaked passwords and source code; Protecting Sensitive Files makes Apache 129 refuse them. Other everyday options: -n (line numbers), -w (whole words), -l (file names), -r --include='*.php', -A/-B/-C (context) and -q (silent, for if grep -q ...). zgrep searches rotated .gz logs (Log Rotation) without unpacking them.