Оптимізація сервера для роботи з MediaWiki
Мета статті: зменшити навантаження на сервер MediaWiki, стабілізувати роботу php8.4-fpm, обмежити агресивних ботів за швидкістю, але не закривати їм доступ повністю.
1. Типова схема роботи
У розглянутій конфігурації сайт працює приблизно так:
Користувач / бот
↓
Frontend Nginx
↓
Backend Nginx
↓
php8.4-fpm
↓
MediaWiki
↓
MySQL / MariaDB
Важливо: якщо перед MediaWiki стоїть ще один Nginx, backend-сервер повинен бачити реальні IP користувачів, а не тільки IP frontend-проксі.
2. Діагностика навантаження
2.1 Перевірка процесів
htop
Або:
ps aux | grep php-fpm | sort -k3 -nr | head -20
Якщо основне навантаження створюють процеси:
php-fpm: pool www
це означає, що навантаження йде через PHP / MediaWiki.
2.2 Топ IP за кількістю запитів
sudo tail -n 50000 /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -nr | head -30
2.3 Топ User-Agent
sudo tail -n 50000 /var/log/nginx/access.log | awk -F\" '{print $6}' | sort | uniq -c | sort -nr | head -30
2.4 Топ URL
sudo tail -n 50000 /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -nr | head -50
Особливо важкі запити для MediaWiki:
action=history
action=edit
action=raw
diff=
oldid=
Special:
api.php
index.php?title=
3. Коректне визначення реального IP через два Nginx
3.1 Frontend Nginx
На frontend Nginx у блоці, який прокидує запити на backend, повинно бути:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
Приклад:
location / {
proxy_pass http://BACKEND_IP;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
send_timeout 300s;
}
3.2 Backend Nginx
На backend Nginx у файлі:
sudo nano /etc/nginx/nginx.conf
у секції http { ... } потрібно додати:
set_real_ip_from 192.168.20.225;
real_ip_header X-Real-IP;
real_ip_recursive on;
де 192.168.20.225 — IP frontend Nginx.
Не можна робити так:
set_real_ip_from 0.0.0.0/0;
Це небезпечно, бо клієнти зможуть підробляти IP через HTTP-заголовки.
3.3 Перевірка real IP
У http { ... } можна тимчасово додати:
log_format realip_debug '$host client=$remote_addr proxy=$realip_remote_addr '
'x_real_ip="$http_x_real_ip" '
'xff="$http_x_forwarded_for" '
'"$request" $status "$http_user_agent"';
У потрібному server { ... }:
access_log /var/log/nginx/wiki_realip_debug.log realip_debug;
Перевірка:
sudo nginx -t
sudo systemctl reload nginx
sudo tail -f /var/log/nginx/wiki_realip_debug.log
Правильний результат:
client=92.222.104.195 proxy=192.168.20.225
Неправильний результат:
client=192.168.20.225
Якщо backend Nginx бачить тільки IP frontend-проксі, то limit_req буде рахувати всіх користувачів і ботів як одного клієнта. Це може викликати 429, затримки або навіть 504.
4. Обмеження швидкості ботів без повного блокування
Завдання — не забороняти ботам доступ через 403, а зменшити швидкість їхніх запитів.
Також важливо, щоб обмеження не зачіпали POST, бо збереження сторінок MediaWiki виконується саме через POST.
4.1 Повний блок для http { }
Цей блок потрібно вставити у:
sudo nano /etc/nginx/nginx.conf
всередину секції http { ... }, бажано до рядків:
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
##
# Real client IP from frontend nginx
##
set_real_ip_from 192.168.20.225;
real_ip_header X-Real-IP;
real_ip_recursive on;
##
# Bot detection
##
map $http_user_agent $is_heavy_bot {
default 0;
~*GPTBot 1;
~*ChatGPT-User 1;
~*ClaudeBot 1;
~*anthropic-ai 1;
~*PerplexityBot 1;
~*Bytespider 1;
~*Amazonbot 1;
~*CCBot 1;
~*MJ12bot 1;
~*AhrefsBot 1;
~*SemrushBot 1;
~*DotBot 1;
~*BLEXBot 1;
~*PetalBot 1;
~*YandexBot 1;
~*Baiduspider 1;
~*meta-webindexer 1;
~*facebookexternalhit 1;
~*Applebot 1;
}
##
# Limit only bot GET/HEAD requests.
# POST is not limited, so MediaWiki save actions are not affected.
##
map "$is_heavy_bot:$request_method" $bot_limit_key {
default "";
"1:GET" $binary_remote_addr;
"1:HEAD" $binary_remote_addr;
}
##
# Heavy MediaWiki query detection
##
map $query_string $mw_heavy_query {
default 0;
~*(^|&)(action=edit|action=history|action=raw|action=info|action=purge) 1;
~*(^|&)(diff=|oldid=|curid=) 1;
~*(^|&)(hidebots=|limit=|from=|target=|namespace=|offset=|dir=) 1;
}
##
# Heavy MediaWiki URI detection
##
map $request_uri $mw_heavy_uri {
default 0;
~*Special: 1;
~*/Special: 1;
~*/wiki/Special: 1;
}
##
# Limit only bots + heavy GET/HEAD requests
##
map "$is_heavy_bot:$mw_heavy_query:$mw_heavy_uri:$request_method" $bot_heavy_key {
default "";
~^1:1:0:GET$ $binary_remote_addr;
~^1:1:0:HEAD$ $binary_remote_addr;
~^1:0:1:GET$ $binary_remote_addr;
~^1:0:1:HEAD$ $binary_remote_addr;
~^1:1:1:GET$ $binary_remote_addr;
~^1:1:1:HEAD$ $binary_remote_addr;
}
##
# Rate limit zones
##
limit_req_zone $bot_limit_key zone=bot_slow:20m rate=1r/s;
limit_req_zone $bot_heavy_key zone=bot_heavy_slow:20m rate=10r/m;
limit_conn_zone $bot_limit_key zone=bot_conn:20m;
Перевага цього блоку: обмежуються тільки боти, тільки GET/HEAD-запити. POST-запити, тобто збереження сторінок, не повинні потрапляти під цей rate limit.
5. Налаштування server-блоку MediaWiki
У конфігу сайту, наприклад:
sudo nano /etc/nginx/sites-available/wiki.erp.kyiv.ua
всередині server { ... } додати:
limit_req_status 429;
limit_conn bot_conn 5;
error_page 429 = @rate_limited;
location @rate_limited {
add_header Retry-After 30 always;
return 429 "Too many requests. Please slow down.\n";
}
У PHP location додати:
limit_req zone=bot_slow burst=20 nodelay;
limit_req zone=bot_heavy_slow burst=5 nodelay;
Повний приклад PHP location:
location ~ \.php$ {
limit_req zone=bot_slow burst=20 nodelay;
limit_req zone=bot_heavy_slow burst=5 nodelay;
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_connect_timeout 60s;
fastcgi_send_timeout 300s;
fastcgi_read_timeout 300s;
}
Увага: якщо PHP-FPM у вас слухає TCP, наприклад 127.0.0.1:9000, не змінюйте fastcgi_pass на socket. Залишайте свій робочий варіант.
6. Чому потрібен nodelay
Без nodelay:
limit_req zone=bot_slow burst=20;
Nginx може затримувати зайві запити в черзі.
З nodelay:
limit_req zone=bot_slow burst=20 nodelay;
дозволені запити проходять одразу, а зайві швидше отримують:
429 Too Many Requests
Рекомендація: для захисту живих користувачів краще використовувати nodelay, щоб Nginx не накопичував довгу чергу запитів.
7. Перевірка Nginx
Після змін:
sudo nginx -t
Якщо все добре:
sudo systemctl reload nginx
Пошук старих або неповних правил:
sudo grep -R "bot_heavy_key\|mw_heavy_uri\|bot_limit_key\|is_heavy_bot\|limit_req" /etc/nginx/
Типові помилки
Помилка:
unknown "bot_heavy_key" variable
Причина: у location використовується limit_req zone=bot_heavy_slow, але в http { } не оголошено $bot_heavy_key.
Помилка:
unknown "mw_heavy_uri" variable
Причина: у конфігу використовується $mw_heavy_uri, але не оголошено відповідний map.
8. Таймаути для уникнення 504
504 Gateway Timeout означає, що Nginx не дочекався відповіді від upstream.
8.1 Frontend Nginx
У location з proxy_pass:
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
send_timeout 300s;
8.2 Backend Nginx
У PHP location:
fastcgi_connect_timeout 60s;
fastcgi_send_timeout 300s;
fastcgi_read_timeout 300s;
Важливо: збільшення timeout не лікує причину повільної роботи, але дає довгим операціям, наприклад збереженню великої сторінки, завершитися без 504.
9. Налаштування php8.4-fpm
Файл pool-конфігурації:
sudo nano /etc/php/8.4/fpm/pool.d/www.conf
Рекомендований стартовий варіант:
pm = dynamic
pm.max_children = 60
pm.start_servers = 10
pm.min_spare_servers = 10
pm.max_spare_servers = 20
pm.max_requests = 500
request_terminate_timeout = 300s
request_slowlog_timeout = 5s
slowlog = /var/log/php8.4-fpm-slow.log
Після зміни:
sudo systemctl restart php8.4-fpm
9.1 Перевірка нестачі воркерів
sudo journalctl -u php8.4-fpm | grep -i "max_children" | tail -20
Якщо є:
server reached pm.max_children setting
потрібно збільшити pm.max_children.
Наприклад:
pm = dynamic
pm.max_children = 80
pm.start_servers = 12
pm.min_spare_servers = 12
pm.max_spare_servers = 30
pm.max_requests = 500
9.2 Як порахувати pm.max_children
Перевірити середній розмір PHP-FPM процесу:
ps aux | grep "php-fpm: pool" | awk '{sum+=$6; n++} END {if (n>0) print "avg:", sum/n/1024, "MB", "count:", n}'
Формула:
pm.max_children = RAM, доступна для PHP-FPM / середній розмір одного PHP-FPM процесу
Приклад:
12000 MB / 180 MB = 66
Тоді можна поставити:
pm.max_children = 60
Не варто віддавати всю RAM під PHP-FPM. Потрібен запас для MySQL/MariaDB, Nginx, системи, файлового кешу та службових задач MediaWiki.
10. PHP-FPM slowlog
У файлі:
sudo nano /etc/php/8.4/fpm/pool.d/www.conf
додати або перевірити:
request_slowlog_timeout = 5s
slowlog = /var/log/php8.4-fpm-slow.log
Перезапуск:
sudo systemctl restart php8.4-fpm
Перегляд:
sudo tail -f /var/log/php8.4-fpm-slow.log
Slowlog допоможе знайти реальну причину повільної роботи. Часто там видно Parser, LinksUpdate, SpecialPage, SyntaxHighlight, api.php, RecentChange або JobQueue.
11. OPcache для PHP 8.4
Файл може бути:
sudo nano /etc/php/8.4/fpm/conf.d/10-opcache.ini
або:
sudo nano /etc/php/8.4/fpm/php.ini
Рекомендовані параметри:
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.save_comments=1
Перезапуск:
sudo systemctl restart php8.4-fpm
Перевірка:
php -i | grep -i opcache
12. APCu для кешу MediaWiki
Встановлення:
sudo apt install php8.4-apcu
Перевірка:
php -m | grep -i apcu
Конфігурація:
sudo nano /etc/php/8.4/fpm/conf.d/20-apcu.ini
apc.enabled=1
apc.shm_size=256M
apc.ttl=3600
apc.user_ttl=3600
apc.gc_ttl=3600
Перезапуск:
sudo systemctl restart php8.4-fpm
У LocalSettings.php:
$wgMainCacheType = CACHE_ACCEL;
$wgSessionCacheType = CACHE_ACCEL;
$wgMessageCacheType = CACHE_ACCEL;
$wgParserCacheType = CACHE_ACCEL;
13. Винесення MediaWiki Job Queue з веб-запитів
У LocalSettings.php:
$wgJobRunRate = 0;
Додати cron від користувача www-data:
sudo crontab -u www-data -e
* * * * * /usr/bin/php /var/www/wiki.erp.kyiv.ua/maintenance/runJobs.php --maxjobs 100 --maxtime 50 > /dev/null 2>&1Це одна з найважливіших оптимізацій для збереження сторінок. MediaWiki jobs не повинні виконуватись у звичайному веб-запиті користувача.
14. File cache MediaWiki
У LocalSettings.php:
$wgUseFileCache = true;
$wgFileCacheDirectory = "$IP/cache";
$wgShowIPinHeader = false;
Створити каталог:
sudo mkdir -p /var/www/wiki.erp.kyiv.ua/cache
sudo chown -R www-data:www-data /var/www/wiki.erp.kyiv.ua/cache
15. FastCGI cache в Nginx для анонімних GET
Увага: FastCGI cache потрібно впроваджувати обережно, щоб не кешувати сторінки авторизованих користувачів.
У http { ... }:
fastcgi_cache_path /var/cache/nginx/mediawiki levels=1:2 keys_zone=MEDIAWIKI:200m inactive=60m max_size=5g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
У PHP location:
set $skip_cache 0;
if ($request_method != GET) {
set $skip_cache 1;
}
if ($query_string != "") {
set $skip_cache 1;
}
if ($http_cookie ~* "UserID|Token|session|mediawiki") {
set $skip_cache 1;
}
location ~ \.php$ {
limit_req zone=bot_slow burst=20 nodelay;
limit_req zone=bot_heavy_slow burst=5 nodelay;
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_connect_timeout 60s;
fastcgi_send_timeout 300s;
fastcgi_read_timeout 300s;
fastcgi_cache MEDIAWIKI;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_valid 404 1m;
add_header X-FastCGI-Cache $upstream_cache_status always;
}
Створити каталог:
sudo mkdir -p /var/cache/nginx/mediawiki
sudo chown -R www-data:www-data /var/cache/nginx/mediawiki
Перевірка:
sudo nginx -t
sudo systemctl reload nginx
curl -I https://wiki.example.com/wiki/Main_Page
Очікуваний заголовок:
X-FastCGI-Cache: HIT
16. robots.txt без повного блокування
Файл:
sudo nano /var/www/wiki.erp.kyiv.ua/robots.txt
Приклад:
User-agent: *
Crawl-delay: 10
Disallow: /w/index.php?title=Special:
Disallow: /*action=edit
Disallow: /*action=history
Disallow: /*action=raw
Disallow: /*diff=
Disallow: /*oldid=
User-agent: GPTBot
Crawl-delay: 30
User-agent: ClaudeBot
Crawl-delay: 30
User-agent: PerplexityBot
Crawl-delay: 30
User-agent: Bytespider
Crawl-delay: 30
User-agent: CCBot
Crawl-delay: 30
User-agent: Amazonbot
Crawl-delay: 30
User-agent: AhrefsBot
Crawl-delay: 30
User-agent: SemrushBot
Crawl-delay: 30
robots.txt не є захистом від агресивних ботів. Але коректні боти можуть враховувати Crawl-delay і не сканувати важкі URL.
17. Кешування статичних файлів
У server { ... }:
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|webp|woff|woff2)$ {
expires 30d;
access_log off;
add_header Cache-Control "public";
}
18. Перевірка MySQL / MariaDB
Поточні запити:
mysqladmin processlist
Загальний статус:
mysqladmin status
Увімкнення slow query log:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
Перегляд:
sudo tail -f /var/log/mysql/mysql-slow.log
Якщо в slow query log багато запитів до таблиць page, revision, text, recentchanges, logging, потрібно окремо аналізувати індекси, розмір таблиць і проблемні сторінки.
19. SyntaxHighlight та інші важкі розширення
Якщо в htop видно процеси:
python3 ... extensions/SyntaxHighlight_GeSHi/...
це означає, що сторінки з підсвіткою коду можуть створювати велике навантаження.
Рекомендації:
- оновити розширення
SyntaxHighlight; - уникати дуже великих блоків
<syntaxhighlight>; - закешувати сторінки з великими блоками коду;
- обмежити ботам швидкість доступу до
oldid,diff,history; - перевірити PHP-FPM slowlog.
20. Діагностика 504 Gateway Timeout
У backend Nginx:
sudo tail -n 100 /var/log/nginx/error.log
Шукати:
upstream timed out while reading response header from upstream
Перевірити кількість кодів відповідей:
sudo tail -n 5000 /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c | sort -nr
Подивитися конкретні URL з 504:
sudo tail -n 5000 /var/log/nginx/access.log | awk '$9 == 504 {print $1, $6, $7, $9, $12}' | tail -50
Якщо 504 виникає при збереженні сторінки, потрібно перевірити:
- чи POST не потрапляє під rate limit;
- чи достатній
proxy_read_timeoutна frontend Nginx; - чи достатній
fastcgi_read_timeoutна backend Nginx; - чи не впирається PHP-FPM у
pm.max_children; - чи не виконуються MediaWiki jobs під час веб-запиту;
- що показує PHP-FPM slowlog.
21. Рекомендований порядок впровадження
Етап 1. Безпечні термінові дії
- Налаштувати
real_ipна backend Nginx. - Додати rate limit тільки для ботів і тільки для
GET/HEAD. - Використати
nodelay. - Перевірити, що
POSTне лімітується. - Збільшити
proxy_read_timeoutіfastcgi_read_timeoutдо300s.
Етап 2. PHP-FPM
- Збільшити
pm.max_children. - Увімкнути slowlog.
- Поставити
request_terminate_timeout = 300s. - Перевірити повідомлення
server reached pm.max_children.
Етап 3. MediaWiki
- Увімкнути APCu.
- Перевірити OPcache.
- Винести Job Queue в cron.
- Увімкнути файловий кеш.
- Проаналізувати важкі розширення.
Етап 4. Кешування Nginx
- Додати кешування статичних файлів.
- Обережно впровадити FastCGI cache для анонімних GET.
- Перевірити, що авторизовані сторінки не кешуються.
Етап 5. База даних
- Увімкнути slow query log.
- Перевірити довгі запити.
- Оптимізувати індекси або проблемні сторінки.
Висновок
Основна ідея оптимізації MediaWiki: не просто збільшити кількість PHP-FPM воркерів, а зменшити кількість важких запитів, які доходять до PHP.
Найважливіші зміни:
- Backend Nginx має бачити реальний IP клієнта.
- Ботів потрібно обмежувати за швидкістю, а не блокувати повністю.
- Rate limit має діяти тільки на
GET/HEAD, не наPOST. - Збереження сторінок не повинно потрапляти під bot limit.
- PHP-FPM має мати достатньо воркерів.
- MediaWiki jobs краще виконувати через cron.
- OPcache, APCu і кешування анонімних сторінок суттєво зменшують навантаження.
- Slowlog PHP-FPM і slow query log MySQL потрібні для пошуку реальної причини затримок.
Після впровадження цих змін сервер має краще витримувати активність ботів, не створюючи проблем для звичайних користувачів і редакторів MediaWiki.