<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Блог &#8211; REMADMIN</title>
	<atom:link href="https://remadmin.com/blog/feed/" rel="self" type="application/rss+xml" />
	<link>https://remadmin.com</link>
	<description>Удалённый системный администратор</description>
	<lastBuildDate>Fri, 11 Sep 2026 14:01:48 +0000</lastBuildDate>
	<language>ru-RU</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>
	<item>
		<title>Разбор заражённого WordPress: порядок действий без магии одного плагина</title>
		<link>https://remadmin.com/blog/vebmasteru/razbor-zarazhjonnogo-wordpress-poryadok-dejstvij-bez-magii-odnogo-plagina/</link>
					<comments>https://remadmin.com/blog/vebmasteru/razbor-zarazhjonnogo-wordpress-poryadok-dejstvij-bez-magii-odnogo-plagina/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Fri, 11 Sep 2026 14:01:48 +0000</pubDate>
				<category><![CDATA[Вебмастеру]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Wordpress]]></category>
		<category><![CDATA[безопасность]]></category>
		<category><![CDATA[бэкдор]]></category>
		<category><![CDATA[вредоносный код]]></category>
		<category><![CDATA[логи]]></category>
		<category><![CDATA[Уязвимость]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7421</guid>

					<description><![CDATA[Как я разбираю взломанный WordPress: снимок и стоп, сверка ядра, mu-plugins и drop-in, PHP в uploads, база и cron, поиск дыры по логам. Почему одного сканера мало и что проверить за ночь.]]></description>
										<content:encoded><![CDATA[<p>Типичная заявка звучит так: «сайт редиректит на казино, в админке куча неизвестных плагинов, Wordfence ничего не нашёл / Wordfence нашёл 400 файлов, я нажал Repair, через сутки опять». Иногда без редиректа: просто «гугл пометил», «хостер прислал abuse», «в Search Console странные URL», «письма клиентам уходят с левыми ссылками».</p>
<p>Я не начинаю с установки ещё одного антивируса в плагины. Сканер внутри заражённого WordPress — это программа, которой уже могли подменить файлы, отключить хуки и спрятать строки в базе. Он полезен как подсказка. Он не является разбором инцидента.</p>
<p>Ниже — порядок, которым я хожу по взломанному WP, когда нужно не «почистить то, что нашёл сканер», а понять, чем держится зараза, откуда зашли и что сделать, чтобы после смены пароля админа сайт не поднял шелл снова через час. Примеры путей и имён — типовые, не с конкретного клиента. Хосты и IP в логах подставляйте свои, чужие из интернета копировать вместе с «готовым лечением» не стоит.</p>
<h5>Сначала стоп, снимок, доступ мимо wp-admin</h5>
<p>Первое желание — зайти в админку и «поудалять левых пользователей». Если в теме или в <code>mu-plugins</code> сидит стилер куку, вы только подтвердите злоумышленнику, что хозяин на сайте, и отдадите свежую сессию.</p>
<p>Пока сайт публичный и пишет в те же файлы, вы затираете улики. Поэтому в первые минуты я делаю три вещи, и только потом открываю редактор.</p>
<p>Снять снимок, который можно откатить. На VPS — снапшот диска у хостера плюс архив каталога сайта и дамп базы на свою машину, не «бэкап плагином на этот же wp-content/uploads». На shared-хостинге — хотя бы <code>tar</code> корня сайта и <code>mysqldump</code> через панель или SSH. Если снимка нет, любое удаление файла потом не докажет, что именно он слал спам.</p>
<p>Посадить сайт на заглушку для посетителей, админку не оставлять открытой в интернет. Варианты, которые работают:</p>
<ul>
<li>В nginx/apache — отдать 503 на все PHP, кроме своего IP. Не «maintenance mode плагином»: его как раз и обходят.</li>
<li>Временно подменить <code>index.php</code> в корне на статическую заглушку, оригинальный файл сохранить как <code>index.php.orig</code> рядом. Корень WP трогать аккуратно: не путать с <code>index.php</code> темы.</li>
<li>На хостинге без SSH — закрыть каталог через панель / <code>.htaccess</code> по IP, если хостер это умеет. Если не умеет — хотя бы сменить пароли FTP и панели <em>до</em> того, как начнёте качать файлы на домашний ПК.</li>
</ul>
<p>Дальше захожу по SSH или SFTP. wp-admin использую только когда уже понял, что сессия оттуда не исполняет чужой PHP. Почту, Telegram-ботов сайта и SMTP-плагин на время инцидента тоже считаю скомпрометированными: с них часто уходит фишинг от имени магазина.</p>
<p>Пока не закрыли вход, параллельно снимаю «кто ещё живой»: соседние сайты на том же аккаунте, cron пользователя, панели ISPmanager/cPanel. Один шелл в соседнем каталоге <code>/var/www/site2</code> через час снова положит «уже почищенный» WP.</p>
<h5>Что считать уликой, а не ощущением</h5>
<p>«Выглядит странно» — плохой критерий. Мне нужны время, путь и факт изменения. Минимальный набор, который я забираю целиком, до того как начну удалять:</p>
<ul>
<li>Корневой <code>wp-config.php</code>, <code>.htaccess</code> в корне, в <code>wp-admin</code> и в <code>uploads</code> (их часто дописывают отдельно).</li>
<li>Drop-in файлы в <code>wp-content</code>: <code>object-cache.php</code>, <code>advanced-cache.php</code>, <code>db.php</code>, <code>sunrise.php</code>. Их сканеры ядра не считают «ядром», и туда любят класть одну строку <code>include</code>.</li>
<li>Каталог <code>wp-content/mu-plugins</code> — must-use подключается всегда, без галочки в админке. Если зараза там, «деактивировать плагины» ничего не делает.</li>
<li>Список пользователей ОС и crontab: <code>crontab -l</code>, <code>/etc/cron.d/</code>, <code>/var/spool/cron/</code>. PHP-шелл, который раз в пять минут дописывает себя в <code>index.php</code>, переживает любую «очистку файлов сайта».</li>
<li>Логи веб-сервера за неделю-две, не за последний час. Нужны access и error, плюс PHP-fpm slowlog, если есть. POST на странный <code>.php</code> в uploads — классика.</li>
</ul>
<p>По файлам смотрю не «все php в uploads», а свежие и с чужим владельцем:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># каталог сайта подставьте свой, не копируйте путь с чужого сервера
SITE=/var/www/example-site/public_html

find "$SITE" -type f -mtime -14 -printf '%TY-%Tm-%Td %TH:%TM %u:%g %p\n' | sort

find "$SITE/wp-content/uploads" -type f \( -name '*.php' -o -name '*.phtml' -o -name '*.phar' -o -name '.*.php' \) -print

find "$SITE" -type f \( -name '.*.ico' -o -name 'wp-*.php' -o -name '*timthumb*' \) -path '*/uploads/*' -print

ls -la "$SITE/wp-content/" "$SITE/wp-content/mu-plugins" 2&gt;/dev/null
ls -la "$SITE/wp-content/"object-cache.php "$SITE/wp-content/"advanced-cache.php "$SITE/wp-content/"db.php 2&gt;/dev/null</pre>
<p>Свежий <code>wp-login.php</code> с датой «сегодня» при том, что ядро вы не обновляли — уже повод сверить контрольные суммы, а не «ну это же ядро, его не трогают». Свежий файл в <code>wp-includes</code> с именем вроде <code>wp-vcd.php</code> / <code>class-wp-http-proxy.php</code> рядом с настоящим классом — ещё чаще.</p>
<p>Владелец тоже врёт, если зараза работает от php-fpm того же пользователя, что и сайт. Тогда все файлы «как надо». Смотрите дату и содержимое, не только <code>ls -l</code>.</p>
<h5>Ядро сверяю с эталоном, плагины — с zip из репозитория</h5>
<p>WordPress умеет сам сказать, какие файлы ядра не совпали. С заражённой админки этой кнопке я не доверяю: подменённый <code>update-core.php</code> вам улыбнётся и покажет зелёное. С хоста, где есть wp-cli и исходящий HTTPS, или просто скачав zip той же версии:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># версию брать из wp-includes/version.php, не «ну наверное 6.4»
grep wp_version "$SITE/wp-includes/version.php"

# на чистой машине:
# curl -fsSLO "https://wordpress.org/wordpress-X.Y.Z.zip"
# unzip и rsync -avnc --delete ядра БЕЗ wp-content и без wp-config.php

wp core verify-checksums --path="$SITE"
wp plugin verify-checksums --all --path="$SITE"
wp theme verify-checksums --all --path="$SITE"</pre>
<p><code>verify-checksums</code> по плагину работает только для того, что лежит в wordpress.org. Платный Elementor, самопис, «нулёвая» тема с warez-сайта — для них эталона нет. Нулёвая тема — отдельный красный флаг: в ней бэкдор часто был <em>с момента установки</em>, и «вирус пришёл сам» тут ни при чём.</p>
<p>Что делать с расхождениями в ядре: не править файл руками «вырезать eval». Заменить пакет ядра целиком из официального zip. Исключения — <code>wp-config.php</code> и каталог <code>wp-content</code>. Если правили сами <code>wp-config.php</code> (соль, префиксы, константы) — файл не из zip, его сверяют глазами, не перезаписывают молча.</p>
<p>Плагин с расхождением: снимаю копию, ставлю тот же zip с wordpress.org. Если плагин платный — zip с кабинета разработчика, не «с форума, он тот же». Самопис, который «нам верстальщик оставил», читаю целиком. Там же часто лежит загрузчик файлов без проверки типа.</p>
<h5>Где прячут PHP, когда «в файлах чисто»</h5>
<p>Поиск по <code>eval(base64_decode</code> — полезный, но наивный. Нормальный PHP тоже бывает некрасивым. Я ищу сочетание: обфускация + место, куда CMS не должна писать исполняемое.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">grep -R --include='*.php' -nE 'eval\s*\(|assert\s*\(|gzinflate\s*\(|gzuncompress\s*\(|str_rot13\s*\(|create_function\s*\(|preg_replace\s*\(.+/e' "$SITE" | head

grep -R --include='*.php' -nE 'FilesMan|c99shell|WSO\s|wp_vcd|wp-tmp|predli|\$GLOBALS\[.as.\]' "$SITE" | head

# одна строка в конце легитимного файла — классика
find "$SITE" -name '*.php' -size -8k -mtime -14 -print

# php внутри картинки / «.ico»
find "$SITE/wp-content/uploads" -type f -name '*.php.*' -o -name '*.jpg.php' -o -name '*.png.php'</pre>
<p>Типовые гнёзда, которые я открываю всегда, даже если grep молчит:</p>
<ul>
<li><code>wp-content/uploads</code> и кэш плагинов: <code>*.php</code> там не должен жить. Исключение — редкие плагины, которые сами кладут PHP в uploads; это повод такой плагин выкинуть, а не «ну так задумано».</li>
<li><code>wp-content/upgrade</code>, <code>wp-content/upgrade-temp-backup</code>, <code>wp-content/blog.dir</code> на мультисайте — мусор после обновлений, его не смотрят.</li>
<li>Старая неактивная тема. «Мы на Astra, Twenty Twenty лежит на всякий» — в Twenty часто и сидит инжект в <code>functions.php</code>. Неактивная тема всё равно исполняется, если её кто-то дернул напрямую по URL.</li>
<li><code>wp-config.php</code> выше корня (правильная схема) <em>и</em> копия <code>wp-config.php</code> в корне/бэкапах <code>*.bak</code>, <code>wp-config.php.old</code>, <code>config.php.save</code>. Через них читают ключи БД без всякого шелла.</li>
</ul>
<p>Отдельно — <code>.htaccess</code>. Не только редирект на чужой домен. Ещё <code>auto_prepend_file</code> / <code>auto_append_file</code>, php_value, RewriteRule на левый файл в uploads, закрытие <code>.php</code> «для всех кроме». Я сравниваю текущий файл с тем, что должен быть у выбранных постоянных ссылок. Если permalinks — «название записи», в корневом htaccess должен быть стандартный кусок WP, а не три килограмма чужих правил.</p>
<p>База. Файловый сканер её не видит. Смотрю:</p>
<ul>
<li>Лишние администраторы в <code>wp_users</code> + <code>wp_usermeta</code> (<code>wp_capabilities</code> = administrator). Пользователь <code>wp_update</code> / <code>wpadmin1</code> / почта на одноразовом домене — не «сотрудник хостера».</li>
<li><code>siteurl</code> и <code>home</code> в <code>wp_options</code>. Если там чужой домен — редирект может быть не в файлах.</li>
<li><code>active_plugins</code>, <code>recently_activated</code>, опции с автозагрузкой <code>autoload=yes</code> и телом в десятки килобайт: туда кладут PHP, который потом <code>eval</code>ят из темы одной строкой.</li>
<li>Контент записей и виджетов: <code>iframe</code>, <code>eval</code>, короткие ссылки, спам-страницы в черновиках и ревизиях. Ревизии чистить отдельно: «удалил пост» не удаляет <code>wp_posts</code> с <code>post_type=revision</code>.</li>
<li>Cron внутри WP: <code>cron</code> в <code>wp_options</code> или <code>wp cron event list</code>. Хук, которого нет ни в одном вашем плагине, раз в 10 минут дергает URL — это не «оптимизация».</li>
</ul>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">wp user list --role=administrator --path="$SITE"
wp option get siteurl --path="$SITE"
wp option get home --path="$SITE"
wp plugin list --path="$SITE"
wp cron event list --path="$SITE"
wp db query "SELECT option_name, LENGTH(option_value) AS sz, autoload FROM wp_options ORDER BY sz DESC LIMIT 30;" --path="$SITE"</pre>
<p>Префикс таблиц берите из <code>wp-config.php</code>, не считайте что он всегда <code>wp_</code>.</p>
<h5>Дыру ищу до «лечения», иначе она зашьёт файл обратно</h5>
<p>Удалить шелл и не закрыть вход — значит пригласить того же бота через пару часов. Пока файлы ещё на месте, по логам видно, <em>какой URL принял POST до появления файла</em>.</p>
<p>Ищу в access-логе не «все 404», а успешные запросы к тому, чего в чистом WP нет, и POST на плагины с известными дырами. Имена ниже — примеры шаблона, не диагноз конкретного сайта:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># nginx: подставьте свой лог
grep -E 'POST /(wp-admin/admin-ajax\.php|xmlrpc\.php|wp-login\.php)' /var/log/nginx/example.access.log | awk '{print $1}' | sort | uniq -c | sort -nr | head

grep -E ' 200 | 302 ' /var/log/nginx/example.access.log | grep -Ei 'uploads/.+\.php|wp-content/plugins/.+/upload|filemanager|wp-setup|radio\.php|about\.php'

# php в uploads, который уже отработал
grep 'wp-content/uploads/.*\.php' /var/log/nginx/example.access.log | grep -E ' 200 | 500 '</pre>
<p>Что чаще всего оказывается входом на обычных магазинах и лендингах, которые мне приносят:</p>
<ul>
<li>Слабый пароль администратора + открытый <code>xmlrpc.php</code> с <code>system.multicall</code>. Брут идёт не через форму логина, лимит на <code>wp-login.php</code> его не видит.</li>
<li>Устаревший плагин формы, слайдера, «добавить товар из Excel», файловый менеджер, который кто-то поставил «на пять минут».</li>
<li>Нулёвая тема / «пак плагинов». Бэкдор там с завода.</li>
<li>Утекла сессия админа: тот же пароль, что в панели хостинга, что в FTP, что в почте. После взлома пароль «тот же, я его недавно менял» меня не успокаивает.</li>
<li>Соседний сайт в том же пользователе UNIX. Дыра была не в этом WP.</li>
</ul>
<p>Версии плагинов фиксирую сразу, до обновления: <code>wp plugin list</code> + даты файлов. Обновить всё «на всякий случай» до разбора — значит затереть следы, какой именно компонент принял запрос. Обновлять надо, но <em>после</em> снимка и записи версий.</p>
<p>Если вход не находится за час — это нормально. Тогда я всё равно меняю секреты и убираю известные дыры, но в отчёте пишу честно: persistence сняли, вектор не доказан. Не пишу «всё закрыто», если в логах дырка.</p>
<h5>Чистить на месте или накатывать чистый слепок</h5>
<p>Правило, которым я пользуюсь. Если есть проверенный бэкап <em>до</em> даты взлома — проще поднять его на чистый каталог, накатить контент (uploads без PHP, дамп таблиц контента без левых пользователей) и заново поставить ядро и плагины из официальных zip. «Лечить» живой каталог имеет смысл, когда бэкапа нет или взлом свежий, и заказчик не может быть сутки в 503.</p>
<p>Чего не делаю:</p>
<ul>
<li>Не ставлю поверх заразы «свежий WordPress» в тот же каталог. Старый <code>mu-plugin</code> и <code>object-cache.php</code> переживут.</li>
<li>Не верю кнопке Repair у сканера как единственному действию. Она вычищает известные сигнатуры. Не вычищает новый include в <code>wp-blog-header.php</code> из трёх символов.</li>
<li>Не оставляю «карантин» плагина внутри <code>wp-content</code>, если этот карантин всё ещё исполняется по прямому URL.</li>
<li>Не правлю обфусцированный файл «вырезать кусок». Файл целиком из эталона или удалить.</li>
</ul>
<p>Порядок очистки на месте, если слепка до взлома нет:</p>
<ol>
<li>Снимок, логи, список админов, crontab, drop-in, mu-plugins — уже сняты.</li>
<li>Ядро заменить официальным zip той же (или уже новой, если дыра в ядре) версии.</li>
<li>Каждый плагин и тему — либо zip с официального источника, либо удалить. Неактивное — тоже удалить с диска, не «выключить».</li>
<li>Из uploads удалить любой PHP/phtml/phar и файлы с двойным расширением. Картинки не «лечить антивирусом», а не исполнять: в nginx для uploads — <code>location ~ \.php$</code> deny.</li>
<li>База: лишние админы, option с длинным autoload, спам-посты и ревизии, подмена siteurl, чужие cron-хуки.</li>
<li>Секреты: ключи в <code>wp-config.php</code> (<code>AUTH_KEY</code> и остальные — сгенерировать заново), пароли всех админов, пароль БД, FTP, панели хостинга, SMTP, токены платёжек и Telegram, application passwords, ключи REST. Сессии: сменить соли, чтобы старые куки умерли.</li>
<li>Только после этого обновлять плагины до текущих версий и включать сайт для своих IP на проверку.</li>
</ol>
<p>Запрет исполнения PHP в uploads — это не «оптимизация», это то, без чего чистка часто бессмысленна:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="nginx">location ~* ^/wp-content/uploads/.*\.php$ {
    deny all;
}</pre>
<p>На apache — в <code>uploads/.htaccess</code> что-то в духе <code>php_flag engine off</code> плюс <code>&lt;FilesMatch&gt;</code> на php. Точный синтаксис зависит от того, mod_php у вас или php-fpm через cgi. Если не уверены — лучше сломать загрузку шелла, чем угадать директиву и оставить выполнение. Проверка простая: положить свой <code>test.php</code> с <code>echo 1;</code> в uploads, открыть в браузере, получить 403/скачивание, файл удалить.</p>
<h5>Проверка, что не всплыло через ночь</h5>
<p>Сайт открываю с своего IP. Смотрю исходник главной и пары внутренних: нет ли внешнего скрипта с левого домена, нет ли <code>display:none</code> простыни ссылок, не подменён ли каноникал. В базе ещё раз <code>siteurl</code>/<code>home</code>. В файлах — даты за последние часы: если после «чистки» снова появились php в uploads, persistence жива (cron, соседний сайт, украденный FTP, незакрытая дыра).</p>
<p>Технически полезно:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># повтор через несколько часов, не сразу
find "$SITE" -type f -mmin -180 -printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort
crontab -l
wp user list --role=administrator --path="$SITE"
wp plugin list --path="$SITE"</pre>
<p>Снаружи: Search Console на странные URL, письмо хостера про исходящий спам, очередь почты на сервере (<code>mailq</code>, логи exim/postfix). Исходящий спам после «уже чистого сайта» почти всегда значит, что PHP-шелл или SMTP-креды живы.</p>
<p>Сканер после этого можно поставить. Один. Как датчик на будущее, не как доказательство чистоты. Я смотрю на него так же, как на fail2ban: хорошо, что пишет, плохо, если им заменяют работу руками.</p>
<p>И ещё: пароли, которые «и так сложные», после инцидента всё равно меняю. В том числе в панели домена и у регистратора. Бывали случаи, когда после чистки сайта через сутки меняли NS — это уже не WordPress, это украденный аккаунт регистратора, и файловый grep этого не увидит.</p>
<h5>Что я не обещаю одним плагином</h5>
<p>Плагин-сканер не видит crontab ОС, соседний vhost, подмену NS и письмо, которое уже ушло ночью. Он плохо видит инжект в базе и drop-in на одну строку. Он может быть сам заражён, если его поставили в уже дырявый WP.</p>
<p>Разбор — это снимок, сверка ядра, поиск persistence, закрытие входа, смена секретов и проверка через несколько часов. Скучно, зато сайт не «зеленеет» в сканере и не краснеет у клиентов на следующий день.</p>
<p>Если нужно разобрать конкретный случай — файлы, логи и доступ по SSH, не «скрин админки и надежда». Это обычная работа по сопровождению, без магии и без прайса на каждый найденный <code>eval</code>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/vebmasteru/razbor-zarazhjonnogo-wordpress-poryadok-dejstvij-bez-magii-odnogo-plagina/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>WireGuard офис ↔ VPS: маршруты, DNS, split-tunnel и проверка что трафик в туннеле</title>
		<link>https://remadmin.com/blog/linux/wireguard-ofis-vps-marshruty-dns-split-tunnel-i-proverka-chto-trafik-v-tunnele/</link>
					<comments>https://remadmin.com/blog/linux/wireguard-ofis-vps-marshruty-dns-split-tunnel-i-proverka-chto-trafik-v-tunnele/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Tue, 08 Sep 2026 20:37:34 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[DNS]]></category>
		<category><![CDATA[VPN]]></category>
		<category><![CDATA[VPS]]></category>
		<category><![CDATA[WireGuard]]></category>
		<category><![CDATA[безопасность]]></category>
		<category><![CDATA[сеть]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7418</guid>

					<description><![CDATA[Как я собираю WireGuard между офисом и VPS: AllowedIPs как маршрут, split-tunnel против 0.0.0.0/0, DNS который не утекает мимо, и проверка tcpdump что рабочий трафик реально в wg0.]]></description>
										<content:encoded><![CDATA[<p>Типичная заявка: «подняли WireGuard на VPS, рукопожатие есть, сайты открываются, а в офис до внутренней админки как не ходили, так и не ходят». Или наоборот: «весь интернет офиса теперь через виртуалку, YouTube тормозит, трафик на тарифе хостера улетел в космос, а до NAS в соседней комнате пакеты почему-то идут через Франкфурт».</p>
<p>Смотрю конфиг — почти всегда одно и то же. На пире стоит <code>AllowedIPs = 0.0.0.0/0</code> «чтобы точно работало», DNS прописан 8.8.8.8, на VPS <code>ip_forward</code> выключен, а проверку сделали командой <code>ping 10.66.66.1</code>. Пинг до адреса туннеля проходит. Это доказывает только то, что два пира видят друг друга. Это не доказывает, что рабочий трафик идёт в туннель, что DNS не утекает мимо, и что обратный путь из офисной сети существует.</p>
<p>Ниже — как я собираю офис <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> VPS так, чтобы маршруты, DNS и split-tunnel не врали, и чем проверяю, что пакет реально ушёл в <code>wg0</code>, а не красиво показал handshake в <code>wg show</code>. Без «поставьте GUI» и без схемы «все в 0.0.0.0/0 и будет счастье».</p>
<h5>Два разных туннеля, которые путают в одном конфиге</h5>
<p>Мне на стол приносят три задачи, и их пытаются решить одним <code>[Peer]</code>:</p>
<ul>
<li>С ноутбука в офисе ходить на сервисы на VPS: SSH, панель, приватный git, админка, которая слушает только внутренний адрес.</li>
<li>С VPS ходить в офисную сеть: NAS, принтер, контроллер, камера, которой «интернет не нужен».</li>
<li>Выгнать весь офисный интернет через VPS, «чтобы как на работе».</li>
</ul>
<p>Это не один туннель с разными галочками. Это разный <code>AllowedIPs</code>, разный форвардинг и разный DNS. Первые две задачи я почти всегда делаю split-tunnel: в туннель уезжает только нужная подсеть. Третья — полный туннель, и за неё отдельно платят CPU, трафиком и сломанным Zoom, если MTU не поправили.</p>
<p>Адреса в примерах ниже документационные, не боевые. Публичный VPS — <code>203.0.113.10</code>, белый офиса — <code>198.51.100.20</code>, туннель — <code>10.66.66.0/24</code>, LAN офиса — <code>192.168.40.0/24</code>. Свои цифры подставьте, чужие из интернета не копируйте вместе с ключами.</p>
<h5>AllowedIPs — это маршрут, а не «список друзей»</h5>
<p>В WireGuard <code>AllowedIPs</code> делает две вещи сразу. Снаружи это криптоключ: пакет с таким источником примут только от этого пира. Изнутри <code>wg-quick</code> по этому списку ставит маршруты. Написали <code>0.0.0.0/0</code> — получите дефолт через туннель (у <code>wg-quick</code> ещё и отдельная таблица, обычно <code>51820</code>). Написали <code>10.66.66.0/24</code> — в туннель пойдёт только эта сеть.</p>
<p>Поэтому «handshake есть, а сайт на VPS открывается по белому IP» — нормально для split-tunnel и баг, если вы хотели полный туннель. Браузер пошёл на <code>203.0.113.10</code> напрямую, UDP 51820 при этом живой. Туннель тут ни при чём.</p>
<p>Смотрю, что система думает про маршрут, не что написано в комментарии к конфигу:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">wg show
ip -4 addr show dev wg0
ip -4 route show
ip -4 route show table all | grep -E 'wg0|51820'
ip -4 route get 10.66.66.1
ip -4 route get 203.0.113.10
ip -4 route get 8.8.8.8</pre>
<p><code>ip route get</code> врёт меньше, чем ощущения. Если до внутреннего адреса VPS маршрут не через <code>wg0</code>, дальше можно не гадать — трафик в туннель не собирался.</p>
<h5>Split-tunnel: офис ходит на VPS, интернет офиса не трогаем</h5>
<p>Это тот вариант, который я ставлю по умолчанию. Офисный пир (ноутбук или маленький шлюз) держит туннель, в <code>AllowedIPs</code> только сеть туннеля и, если надо, приватные сети на стороне VPS. Исходящий веб офиса идёт как раньше, через местного провайдера.</p>
<p>VPS:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="ini">[Interface]
Address = 10.66.66.1/24
ListenPort = 51820
PrivateKey = &lt;ключ VPS&gt;

[Peer]
PublicKey = &lt;ключ офиса&gt;
AllowedIPs = 10.66.66.2/32</pre>
<p>Офис (за NAT, поэтому keepalive):</p>
<pre class="EnlighterJSRAW" data-enlighter-language="ini">[Interface]
Address = 10.66.66.2/24
PrivateKey = &lt;ключ офиса&gt;

[Peer]
PublicKey = &lt;ключ VPS&gt;
Endpoint = 203.0.113.10:51820
AllowedIPs = 10.66.66.0/24
PersistentKeepalive = 25</pre>
<p>Если на VPS сервисы торчат в docker-сети или во втором внутреннем интерфейсе, в <code>AllowedIPs</code> офиса добавляю <em>эту</em> сеть, не «весь мир». И на VPS тогда уже нужен форвардинг и фильтр, иначе пакет дойдёт до ядра и умрёт в <code>FORWARD</code>.</p>
<p>На VPS для split-tunnel NAT наружу обычно не нужен. Нужен, когда офис должен ходить не на сам VPS, а <em>через</em> него дальше. Пока задача «SSH на 10.66.66.1» — без маскарада.</p>
<p><code>PersistentKeepalive = 25</code> ставлю на том конце, который за NAT или с динамическим белым. Без него UDP-маппинг у провайдера офиса схлопывается, handshake в <code>wg show</code> становится старше пары минут, и «туннель встал сам» ровно до первой попытки зайти с другой стороны.</p>
<h5>Когда VPS должен видеть LAN офиса</h5>
<p>Вторая заявка: бэкап с VPS на NAS, мониторинг контроллера, SSH на станок, который «в интернет не смотрит». Тогда на стороне VPS в пире офиса мало <code>/32</code> туннельного адреса. Нужна офисная подсеть:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="ini"># на VPS, peer = офисный шлюз
[Peer]
PublicKey = &lt;ключ офисного шлюза&gt;
AllowedIPs = 10.66.66.2/32, 192.168.40.0/24</pre>
<p>Офисный шлюз должен форвардить. Это уже не «WireGuard на ноутбуке бухгалтера». Ноутбук не маршрутизатор: пакет с VPS на <code>192.168.40.15</code> дойдёт до ноутбука и останется там, NAS его не увидит, а ответ NAS пойдёт в локальный шлюз офиса и умрёт, потому что шлюз про <code>10.66.66.0/24</code> ничего не знает.</p>
<p>На шлюзе офиса:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">sysctl -w net.ipv4.ip_forward=1
# постоянное место — /etc/sysctl.d/, не «в сессии до ребута»

iptables -C FORWARD -i wg0 -o eth0 -s 10.66.66.0/24 -d 192.168.40.0/24 -j ACCEPT 2&gt;/dev/null || \
iptables -A FORWARD -i wg0 -o eth0 -s 10.66.66.0/24 -d 192.168.40.0/24 -j ACCEPT
iptables -C FORWARD -i eth0 -o wg0 -s 192.168.40.0/24 -d 10.66.66.0/24 -j ACCEPT 2&gt;/dev/null || \
iptables -A FORWARD -i eth0 -o wg0 -s 192.168.40.0/24 -d 10.66.66.0/24 -j ACCEPT</pre>
<p>Имена интерфейсов свои. <code>eth0</code> в статье — заглушка. На машине смотрю <code>ip -br link</code>, не копирую с чужого скриншота.</p>
<p>Обратный маршрут в LAN часто забывают. NAS отвечает на запрос с <code>10.66.66.1</code> через дефолтный шлюз офиса. Шлюз этот пакет не ждал — в лучшем случае дроп, в худшем NAT в интернет. Варианты, которые работают:</p>
<ul>
<li>Статический маршрут на роутере офиса: <code>10.66.66.0/24 via 192.168.40.2</code>, где <code>.2</code> — шлюз с WireGuard.</li>
<li>Или маскарад на офисном WG-хосте в сторону LAN, если роутер трогать нельзя. Работает, в логах NAS все запросы с одного адреса шлюза. Для бэкапа терпимо, для разбора «кто стучится» — нет.</li>
</ul>
<p>Пересечение подсетей ловлю сразу. Офис на <code>192.168.1.0/24</code>, docker на VPS тоже <code>192.168.1.0/24</code> — туннель поднимется, маршруты будут врать по очереди. Одну из сетей надо сменить. Спорить, «чья 192.168.1 важнее», бесполезно: важнее та, которую проще перенумеровать.</p>
<h5>Полный туннель: 0.0.0.0/0, таблица 51820 и NAT на VPS</h5>
<p><code>AllowedIPs = 0.0.0.0/0</code> на офисном пире означает: весь IPv4, кроме того что <code>wg-quick</code> сам вычтет для Endpoint, должен идти в туннель. <code>wg-quick</code> для этого поднимает policy routing, не просто <code>ip route add default</code>. Поэтому «я убрал default из main, а туннель живой» — ещё не победа. Смотреть надо <code>ip rule</code> и таблицу, которую создал WireGuard:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ip rule
ip route show table 51820
ip route get 8.8.8.8
ip route get 203.0.113.10</pre>
<p>До Endpoint маршрут обязан остаться в физический интерфейс. Иначе туннель пытается нести сам себя и тихо умирает после первого же пересчёта маршрутов.</p>
<p>На VPS для чужого интернета нужны три вещи, не одна:</p>
<ul>
<li><code>net.ipv4.ip_forward=1</code></li>
<li>NAT с туннеля в внешний интерфейс, иначе провайдер VPS не пропустит чужие RFC1918 как source</li>
<li>FORWARD в файрволе в обе стороны, не только INPUT на 51820/udp</li>
</ul>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># на VPS, внешний интерфейс уточнить: ip -br addr
sysctl -w net.ipv4.ip_forward=1

iptables -t nat -C POSTROUTING -s 10.66.66.0/24 -o ens3 -j MASQUERADE 2&gt;/dev/null || \
iptables -t nat -A POSTROUTING -s 10.66.66.0/24 -o ens3 -j MASQUERADE

iptables -C FORWARD -i wg0 -o ens3 -j ACCEPT 2&gt;/dev/null || iptables -A FORWARD -i wg0 -o ens3 -j ACCEPT
iptables -C FORWARD -i ens3 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2&gt;/dev/null || \
iptables -A FORWARD -i ens3 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT</pre>
<p>Правила в статье — эскиз под iptables. На машине с nftables я пишу в ту таблицу, которая уже живая, а не добавляю второй файрвол «рядом». <code>ufw route allow</code> и <code>iptables -A FORWARD</code> одновременно — классический способ получить «у меня же разрешено», когда пакет режет другая цепочка.</p>
<p>Полный туннель я не ставлю «на всякий случай». Он нужен, когда офисный канал дырявый, нужен выход с фиксированного белого VPS, или политика «весь трафик только через нас». Для доступа к одной админке на VPS это дорогой способ сломать DNS, MTU и чужой Zoom.</p>
<h5>DNS: туннель живой, а имена резолвятся мимо</h5>
<p>Самая частая ложь после <code>wg show</code>. Туннель есть, <code>curl https://10.66.66.1</code> работает, а <code>curl https://panel.internal</code> идёт неизвестно куда или не идёт вовсе. Причина почти всегда в резолвере, не в маршрутах.</p>
<p><code>DNS =</code> в <code>wg-quick</code> — не магия. На systemd-resolved это попытка прикрутить домены к интерфейсу. На машине без resolved это может записаться в <code>/etc/resolv.conf</code> и затереть офисный DNS. На Windows-клиенте это отдельная история с «использовать DNS этого адаптера». Я не верю строке в конфиге, пока не увижу, куда реально уходит запрос.</p>
<p>Проверка с офисного пира:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">resolvectl status
resolvectl query panel.internal
getent hosts panel.internal

# куда уходит UDP/53, пока стучите в имя
tcpdump -ni wg0 port 53
tcpdump -ni eth0 port 53</pre>
<p>Если дамп на физическом интерфейсе показывает запросы на <code>8.8.8.8</code>, а на <code>wg0</code> тихо — split-tunnel сделал ровно то, что вы написали: маршрут до публичного DNS прямой. Внутреннее имя при этом либо не резолвится, либо резолвится в публичный адрес, и вы снова обходите туннель.</p>
<p>Рабочая схема для split-tunnel, которой я пользуюсь:</p>
<ul>
<li>На VPS крутится DNS, который знает внутренние имена. Слушает <code>10.66.66.1</code>, не <code>0.0.0.0</code> в интернет.</li>
<li>У офисного пира <code>DNS = 10.66.66.1</code>, и этот адрес уже входит в <code>AllowedIPs</code>.</li>
<li>Если нужен только суффикс <code>internal</code>, в resolved указываю домен на интерфейс туннеля, а не подменяю весь резолвер офиса.</li>
</ul>
<p>Для полного туннеля публичный DNS тоже должен быть достижим <em>через</em> туннель. Иначе получаете курицу и яйцо: чтобы поднять туннель, нужен DNS Endpoint, а DNS вы уже отправили в туннель, которого нет. Поэтому Endpoint я пишу IP, не именем. Имя в Endpoint оставляю только там, где белый VPS прыгает и без DNS туннель не собрать — тогда DNS офиса должен резолвить этот хост <em>до</em> подъёма <code>wg0</code>.</p>
<h5>IPv6 утекает отдельно, MTU ломает «сайты открываются через раз»</h5>
<p>Настроили IPv4, про IPv6 забыли. Браузер берёт AAAA, пакеты идут мимо туннеля в родной канал. <code>curl -4</code> при этом красивый. Если полный туннель — в <code>AllowedIPs</code> нужен и <code>::/0</code>, и на VPS форвардинг/NAT6, либо IPv6 на офисном пире надо выключить осознанно, не «само как-нибудь».</p>
<p>Для split-tunnel я проверяю оба стека:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ip -6 route get 2001:db8::1
curl -4 -s https://ifconfig.me
curl -6 -s https://ifconfig.me</pre>
<p>Документационный <code>2001:db8::1</code> в <code>route get</code> нужен, чтобы увидеть, в какой интерфейс ядро отправит «чужой» v6. Если полного туннеля нет, прямой маршрут — ожидаемо. Если полный туннель заявлен, а v6 уходит в <code>eth0</code> — дыра.</p>
<p>MTU. UDP + WireGuard + внешний Ethernet обычно просит 1420, на части каналов — ниже. Симптом не «туннель мёртв», а «SSH живой, HTTPS висит, крупные POST отваливаются, мессенджеры отваливаются на картинках». Ловлю так:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ip link show wg0
ping -M do -s 1372 10.66.66.1
ping -M do -s 1200 10.66.66.1</pre>
<p>Если 1372 с DF не проходит, опускаю MTU на <code>wg0</code>, пока не начнёт. На VPS с jumbo или с PPPoE за NAT цифры другие — подбираю, не копирую 1280 «потому что все так пишут», если канал нормально держит больше.</p>
<h5>Проверка, что рабочий трафик в туннеле, а не только handshake</h5>
<p>Чеклист, которым я пользуюсь после <code>wg-quick up</code>. По порядку, не выборочно.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># 1. пир живой
wg show
# handshake секунды-минуты, не часы; transfer растёт, когда вы сами генерируете трафик

# 2. адрес и маршрут
ip route get 10.66.66.1
# ожидаю dev wg0

# 3. ICMP внутри туннеля — необходимое, но не достаточное
ping -c 3 10.66.66.1

# 4. TCP на сервис, который слушает только внутренний адрес
curl -v --connect-timeout 5 http://10.66.66.1:8080/
ss -tlnp | grep 8080   # это уже на VPS: сервис не на 0.0.0.0:80 случайно?</pre>
<p>Дальше — дамп. Это единственный способ перестать спорить. На офисном пире в одном окне генерирую трафик, в двух других:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">tcpdump -ni wg0 host 10.66.66.1
tcpdump -ni eth0 udp port 51820
tcpdump -ni eth0 host 203.0.113.10 and not udp port 51820</pre>
<p>Нормальная картина split-tunnel, когда я хожу на внутренний сервис: на <code>wg0</code> видны пакеты до <code>10.66.66.1</code>, на физическом интерфейсе до VPS — только UDP 51820. Если на физическом интерфейсе всплыл обычный TCP на белый <code>203.0.113.10:443</code>, вы открыли публичный nginx, не туннель.</p>
<p>Для полного туннеля добавляю:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># с офисного пира
curl -4 -s https://ifconfig.me
# должен вернуться белый VPS, не белый офиса

ip route get 1.1.1.1
# dev wg0 или таблица 51820, не eth0

tcpdump -ni eth0 tcp port 443
# во время curl на внешний сайт здесь не должно быть голого TCP
# только UDP 51820</pre>
<p>Если <code>ifconfig.me</code> показал белый офиса — трафик мимо. Если показал белый VPS, но на <code>eth0</code> всё равно виден TCP 443 — смотрю второй интерфейс, IPv6, браузерный DoH, «умный» антивирус со своим туннелем. Бывает, что <code>curl</code> в туннеле, а Firefox живёт своей жизнью.</p>
<p>На самой VPS в это время:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">tcpdump -ni wg0
tcpdump -ni ens3 udp port 51820
# conntrack, если ping есть, а TCP нет
conntrack -L | grep 10.66.66</pre>
<p>Ещё одна ловушка: сервис на VPS слушает <code>127.0.0.1</code> или только публичный адрес. Туннель доставляет пакет на <code>10.66.66.1</code>, nginx его не принимает. Это не «WireGuard сломался». Это <code>ss -tlnp</code> до настройки туннеля, не после.</p>
<h5>Что я проверяю, когда «подключилось, но не работает»</h5>
<p>Короткий разбор по симптомам, без перебора всего man.</p>
<ul>
<li>Handshake старше нескольких минут, transfer ноль — NAT, файрвол хостера на UDP 51820, неверный Endpoint, часы сильно разъехались реже, но бывает. С офиса: <code>nc -u -v 203.0.113.10 51820</code> ничего не доказывает (UDP без ответа), нужен дамп на VPS в момент <code>wg-quick up</code>.</li>
<li>Handshake живой, ping до <code>10.66.66.x</code> нет — AllowedIPs не покрывает адрес, или адреса туннеля пересеклись у двух пиров. Два клиента с <code>10.66.66.2/24</code> одновременно — классика копипаста.</li>
<li>Ping есть, TCP нет — MTU, или сервис не слушает адрес туннеля, или FORWARD/фильтр режет не ICMP, а tcp.</li>
<li>До VPS по внутреннему IP есть, до NAS в офисе нет — на VPS в AllowedIPs нет LAN, на офисе нет forward, на роутере офиса нет обратного маршрута.</li>
<li>Браузер открывает сайт по домену «не туда» — DNS мимо туннеля или A-запись смотрит на белый, а вы думали, что ходите внутрь.</li>
<li>После сна ноутбука туннель «есть», трафика нет — keepalive, stale UDP mapping, помогает <code>wg-quick down; wg-quick up</code>, а постоянно — keepalive и иногда <code>NetworkManager</code> dispatcher, не вечная сессия с 2023 года.</li>
</ul>
<p>Ключи и чужие IP в тикет не кладу. В логах для разбора достаточно последних байт публичного ключа из <code>wg show</code> и документационных адресов. Боевой Endpoint заказчика в статью и в переписку с подрядчиком не тащу.</p>
<h5>Что оставляю в конфиге, когда уже заработало</h5>
<p>Минимальный набор, который я не выкидываю «потом допишем»:</p>
<ul>
<li>Endpoint только IP, порт явно 51820 или тот, что реально открыт у хостера. У части облаков UDP на дефолтном порту режут, тогда слушаю другой и это же пишу в Endpoint.</li>
<li><code>AllowedIPs</code> по задаче, не максимальный. Split-tunnel — сети, которые правда надо унести. Полный туннель — осознанный <code>0.0.0.0/0</code> плюс IPv6-решение.</li>
<li><code>PersistentKeepalive</code> на NAT-стороне.</li>
<li>DNS только тот, который достижим через получившиеся маршруты.</li>
<li>Проверка дампом один раз после подъёма, не «пинг прошёл — закрываем задачу».</li>
</ul>
<p>Конфиг без этой проверки — это VPN на скриншоте. Скриншот <code>wg show</code> с handshake я в отчёт тоже прикладываю, но рядом кладу <code>ip route get</code> до рабочего адреса и кусок tcpdump, где видно UDP 51820 снаружи и целевой трафик внутри. Если второго нет, туннеля для работы тоже нет — есть только красивый интерфейс <code>wg0</code>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/linux/wireguard-ofis-vps-marshruty-dns-split-tunnel-i-proverka-chto-trafik-v-tunnele/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Скандинавский свет на Opel Astra H: UEC Variant 5 + REC Variant 1</title>
		<link>https://remadmin.com/blog/auto/skandinavskij-svet-na-opel-astra-h-uec-variant-5-rec-variant-1/</link>
					<comments>https://remadmin.com/blog/auto/skandinavskij-svet-na-opel-astra-h-uec-variant-5-rec-variant-1/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Sun, 06 Sep 2026 13:25:30 +0000</pubDate>
				<category><![CDATA[Авто]]></category>
		<category><![CDATA[Daytime Running Light]]></category>
		<category><![CDATA[OP-COM]]></category>
		<category><![CDATA[Opel Astra H]]></category>
		<category><![CDATA[REC]]></category>
		<category><![CDATA[UEC]]></category>
		<category><![CDATA[скандинавский свет]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7413</guid>

					<description><![CDATA[Как включить скандинавский свет на Astra H 2008 через OP-COM: почему одной таблицы из интернета мало, зачем отдельно программировать UEC и REC, и какая связка вариантов реально зажигает ближний и задние габариты после запуска двигателя.]]></description>
										<content:encoded><![CDATA[<p>На Opel Astra H скандинавский свет включается не реле, как на старых Zafira A, а программированием блоков через OP-COM / Tech2. Казалось бы — открыл таблицу вариантов Daytime Running Light, выбрал нужную строку, и готово. На практике у нас (Astra H, 2008, универсал, бензин) всё оказалось чуть хитрее: таблица есть почти в каждом гайде, а задние габариты сами по себе всё равно не загорались. Разбираю, почему так и какой связкой UEC + REC в итоге получилось то, что нужно.</p>
<p>Нужный результат простой: крутилка света в «0» (или Auto), при включении зажигания ничего лишнего не горит, после запуска двигателя автоматически ближний спереди и габариты сзади. Штатный ручной свет при этом остаётся как был.</p>
<h5>Та самая таблица, которую все копируют</h5>
<p>В интернете по Astra H / Zafira B гуляет одна и та же схема вариантов DRL — «Стандарт» и Version 1–5 в разрезе «зажигание» / «мотор заведён» и положения переключателя. Её перепечатывают на Drive2, в FAQ по OP-COM, на чешских и немецких форумах. Мы тоже от неё оттолкнулись.</p>
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="960" height="566" class="wp-image-7411" src="https://remadmin.com/wp-content/uploads/2026/09/astra-h-drl-variants-table.jpg" alt="Таблица вариантов скандинавского света Opel Astra H — Стандарт и Version 1–5" srcset="https://remadmin.com/wp-content/uploads/2026/09/astra-h-drl-variants-table.jpg 960w, https://remadmin.com/wp-content/uploads/2026/09/astra-h-drl-variants-table-300x177.jpg 300w, https://remadmin.com/wp-content/uploads/2026/09/astra-h-drl-variants-table-768x453.jpg 768w" sizes="(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /><figcaption>Классическая таблица вариантов DRL для Astra H / Zafira B — её копируют почти во всех гайдах.</figcaption></figure>
<p>По таблице кажется, что достаточно один раз выбрать Version 1 или Version 5 — и поведение «всё включится как надо». На деле таблица описывает <em>логику варианта</em>, а не то, что перед и зад программируются в разных блоках. Из‑за этого половина инструкций заканчивается на UEC, а про REC либо вскользь, либо никак.</p>
<h5>Что получилось у нас на Version 1 и 5</h5>
<p>Пробовали классику — ставили одинаковый вариант и смотрели глазами.</p>
<ul>
<li><strong>Version 1:</strong> при включении зажигания уже горели передние габариты; после запуска двигателя они оставались, плюс включался ближний. Для «дневного» режима это слишком рано: ещё не завёл — уже светится.</li>
<li><strong>Version 5:</strong> при зажигании ничего, после запуска двигателя зажигается ближний..</li>
</ul>
<p>И главное: <strong>задние габариты автоматически не включались ни в одном из этих режимов</strong>, пока трогали только то, что обычно трогают первым — передний блок. Спереди картина менялась, сзади — тишина. Вот тут и всплывает архитектура Astra H.</p>
<h5>Почему перед и зад настраиваются отдельно</h5>
<p>На Astra H (и близкой по электрике Zafira B) свет и кузовная периферия разнесены по двум «центрам»:</p>
<ul>
<li><strong>UEC</strong> (Underhood Electrical Center, иногда FZM / Front Zone Module) — подкапотный блок. Передние фары, часть передней кузовной логики, датчик дождя/света и параметр <em>Daytime Running Light</em> именно для передка.</li>
<li><strong>REC</strong> (Rear Electrical Center, RZM / Rear Zone Module) — задний блок (обычно слева сзади). Задние фонари, центральный замок, охранка и <em>свой</em> такой же пункт Daytime Running Light.</li>
</ul>
<p>Это не «одна настройка на всю машину». У каждого модуля свой Variant Configuration и свой CarPass-ввод. Можно поставить на UEC Version 5, а на REC оставить Disabled / Standard — и получите ровно нашу картину: спереди DRL ожил, сзади габариты ждут ручной крутилки. Таблица из интернета этого почти не объясняет: там одна строка «версия», как будто она глобальная.</p>
<p>Отсюда и редкий, но рабочий трюк: <strong>разные варианты на перед и зад</strong>. Мы не изобретали велосипед с нуля — в FAQ по OP-COM давно пишут, что DRL надо прописывать и в UEC, и в REC. А вот комбинацию «5 спереди + 1 сзади» под конкретное поведение мало где разжёвывают.</p>
<h5>Что понадобится</h5>
<ul>
<li>Диагностика с поддержкой Opel: OP-COM (качественный клон/оригинал) или Tech2 + CANdi.</li>
<li><strong>CarPass</strong> — четыре цифры security code с карточки, которую выдавали с машиной. Без него Program Variant Configuration не пустит. Потеряли — только через дилера / сервис с доступом к базе (это не «взлом», а штатная защита кодировки).</li>
<li>Зажигание включено (шина живая), аккумулятор не на нуле.</li>
<li>Желательно сохранить текущий конфиг кнопкой Save в OP-COM, если она есть — чтобы откатить, если вариант не зайдёт.</li>
</ul>
<h5>Как мы запрограммировали перед (UEC) — Variant 5</h5>
<p>Путь в OP-COM:</p>
<p><code>Diagnostics → год выпуска → Astra-H → Body → UEC → Programming → Program Variant Configuration → CarPass (4 цифры) → Daytime Running Light → Variant 5 → Program</code></p>
<p>Variant 5 на передке как раз ближе к «скандинавскому» дневном режиму: не тянет габариты и приборку так, как ранние версии при одном только зажигании, и даёт ближний уже в логике DRL. Сам по себе он нам ещё не закрыл задачу — но как половинка связки сработал отлично.</p>
<p>Нюансы из заводских/форумных заметок по Version 5 (имейте в виду): пока DRL активен, подсветка дисплея/кнопок может вести себя иначе, чем в ручном ближнем; дальний по таблице включается только из положения переключателя «ближний»; при крутилке на габаритах DRL обычно глушится. Если стоит ксенон — на Version 2–4 в гайдах часто советуют не заигрывать (свет может гореть ещё до запуска мотора).</p>
<h5>Как настроили зад (REC) — Variant 1</h5>
<p>Тот же ритуал, другой блок:</p>
<p><code>Diagnostics → год выпуска → Astra-H → Body → REC → Programming → Program Variant Configuration → CarPass → Daytime Running Light → Variant 1 → Program</code></p>
<p>Именно <strong>Variant 1 на REC</strong> у нас наконец поднял задние габариты в автоматике. Пока REC оставался без своего DRL-варианта (или на «стандарте»), сзади тишина — сколько ни крути UEC.</p>
<p>После Program иногда помогает коротко выключить зажигание и снова завести, чтобы модули перечитали кодировку.</p>
<h5>Итог на нашей машине</h5>
<p>Связка <strong>UEC = Variant 5</strong> + <strong>REC = Variant 1</strong> на Astra H 2008 универсал бензин дала то, что хотели:</p>
<ul>
<li>зажигание включено, мотор не заведён — ничего не горит;</li>
<li>двигатель запущен — автоматически ближний спереди и задние габариты;</li>
<li>ручной режим света как штатный, без сюрпризов.</li>
</ul>
<p>Если у вас другой год / хэтч / GTC / ксенон / датчик света — цифры вариантов могут понадобиться другие, но принцип тот же: <strong>не программируйте только UEC и не ждите чудес от задних фонарей</strong>. Сначала таблица из интернета, потом обязательно REC, и уже подбором (или нашей связкой 5+1) ловите нужную картину.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/auto/skandinavskij-svet-na-opel-astra-h-uec-variant-5-rec-variant-1/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Nginx: лимиты на wp-login и xmlrpc без отстрела нормальных пользователей</title>
		<link>https://remadmin.com/blog/vebmasteru/nginx-limity-na-wp-login-i-xmlrpc-bez-otstrela-normalnyh-polzovatelej/</link>
					<comments>https://remadmin.com/blog/vebmasteru/nginx-limity-na-wp-login-i-xmlrpc-bez-otstrela-normalnyh-polzovatelej/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Sun, 06 Sep 2026 12:57:32 +0000</pubDate>
				<category><![CDATA[Вебмастеру]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[Wordpress]]></category>
		<category><![CDATA[wp-login]]></category>
		<category><![CDATA[xmlrpc]]></category>
		<category><![CDATA[безопасность]]></category>
		<category><![CDATA[спам]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7407</guid>

					<description><![CDATA[Как я режу брутфорс на wp-login.php и xmlrpc.php через nginx limit_req, не закрывая офис за NAT и не превращая отказ в 503 по всему PHP. Отдельные зоны, POST-only, real_ip и почему system.multicall лимитер не видит.]]></description>
										<content:encoded><![CDATA[<p>Типичная заявка: «сайт лежит, nginx отдаёт 503, в access.log сплошной wp-login.php». Открываю лог — не DDoS на главную, а брутфорс. Сотни POST в минуту на <code>wp-login.php</code> и <code>xmlrpc.php</code> с пары десятков адресов. Человек уже воткнул <code>limit_req</code> на весь <code>location ~ \.php$</code> «на всякий случай». Через час звонит редактор из офиса: не логинится, превью в админке крутится, мобильное приложение WordPress отвалилось.</p>
<p>Лимит сработал. Просто он отстрелил своих вместе с ботами. Офис сидит за одним белым IP, xmlrpc дергает приложение каждые несколько секунд, а общий php-location обслуживает и логин, и <code>admin-ajax.php</code>, и оформление заказа.</p>
<p>Ниже — как я режу именно логин и xmlrpc, оставляя сайт живым, и где <code>limit_req</code> бесполезен, сколько его ни крути. Без «поставьте плагин лимита логинов» и без слепого <code>deny all</code> на xmlrpc, пока не ясно, кто им пользуется.</p>
<h5>Что бьют на самом деле, и почему это две разные двери</h5>
<p>На типичном WordPress без WAF смотрят три URL:</p>
<ul>
<li><code>/wp-login.php</code> — форма входа. GET рисует форму, POST проверяет пароль.</li>
<li><code>/xmlrpc.php</code> — старый API. С него удобно перебирать пароли пачками через <code>system.multicall</code> и дёргать pingback.</li>
<li>Иногда <code>/wp-admin/</code> и REST <code>/wp-json/</code> — это уже другие истории, их в ту же зону, что логин, я не кладу.</li>
</ul>
<p>GET на логин — не атака. Человек открыл форму, браузер подтянул редирект, плагин 2FA нарисовал второй шаг. Если лимитировать любой метод, первый же заход «съедает» burst, а POST с паролем уже ловит 429. Поэтому ключ зоны я завязываю на POST, не на URL целиком.</p>
<p>xmlrpc почти всегда POST. Закрывать его наглухо можно, если им никто не пользуется: нет официального приложения WP, нет Jetpack, нет старых клиентов публикации. Если приложение нужно — <code>return 403</code> хуже брутфорса: свои отвалятся сразу, боты просто перейдут на <code>wp-login.php</code>.</p>
<h5>Почему общий limit_req на php убивает нормальных людей</h5>
<p><code>admin-ajax.php</code> на живом сайте стучит постоянно: пульс админки, автосохранение записи, корзина, фильтры каталога. Один редактор за минуту легко делает десятки POST. Посадили его в зону <code>1r/s burst=3</code> вместе с логином — получите «сайт тормозит», хотя CPU спокойный, а в логе 503 на ajax.</p>
<p>Вторая ловушка — общий IP. Офис, коворкинг, 4G-оператор с CGNAT, гостиница. Двадцать человек за одним адресом. Лимит «один запрос в секунду на IP» для брутфорса с ботнета ещё терпим, для офиса это коллективный бан после трёх неудачных паролей подряд.</p>
<p>Третья — статус по умолчанию. Nginx на превышение отдаёт 503. Мониторинг орёт «сайт мёртв», человек лезет в php-fpm и диск. Это не падение бэкенда, это отказ лимитера. Ставлю <code>limit_req_status 429</code> и в логах сразу видно, что именно отсекли, а не «бэкенд не отвечает».</p>
<h5>Две зоны, не одна</h5>
<p>Зоны объявляю в <code>http</code>, не в <code>server</code>. Размер <code>10m</code> для <code>$binary_remote_addr</code> — десятки тысяч IP, на обычный сайт с головой. Rate считаю отдельно:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="nginx"># http { ... }
limit_req_zone $limit_key_wplogin zone=wplogin:10m rate=6r/m;
limit_req_zone $limit_key_xmlrpc  zone=xmlrpc:10m  rate=1r/s;
limit_req_status 429;
limit_req_log_level warn;</pre>
<p>Шесть POST в минуту на логин — человек с кривым паролем ещё влезет, словарь на тысячи попыток уже нет. xmlrpc чуть свободнее по частоте HTTP-запросов: живое приложение иногда стучит пачкой. Это не дыра — дыра в xmlrpc не частота, а толстое тело, про это ниже.</p>
<p>Ключ пустой строкой означает «этого запроса в зоне нет». Так я выключаю лимит для GET и для белого списка.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="nginx">map $request_method $limit_key_wplogin {
    POST $binary_remote_addr;
    default "";
}

# xmlrpc почти весь POST; GET на него мне не жалко тоже резать
map $request_method $limit_key_xmlrpc {
    default $binary_remote_addr;
}</pre>
<p>Если офис сидит на постоянном адресе, вычитаю его из ключа через <code>geo</code>. В примере — документационный диапазон, не чей-то боевой:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="nginx">geo $offnet {
    default 0;
    203.0.113.0/24 1;   # офис, подставьте свой
}

map $offnet $limit_key_wplogin {
    1      "";
    default $limit_wplogin_ip;
}

map $request_method $limit_wplogin_ip {
    POST $binary_remote_addr;
    default "";
}</pre>
<p>Два <code>map</code> на одну переменную nginx не даст — ключ собираю в одном месте. Рабочий вариант без сюрпризов:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="nginx">geo $is_office {
    default 0;
    203.0.113.0/24 1;
}

map "$is_office:$request_method" $limit_key_wplogin {
    "1:POST" "";
    "0:POST" $binary_remote_addr;
    default  "";
}

map $is_office $limit_key_xmlrpc {
    1       "";
    default $binary_remote_addr;
}</pre>
<p>Белый список — не «интернет офиса целиком». Только тот префикс, с которого реально заходят в админку. Домашний динамический IP туда не тащу: через месяц он уедет соседу.</p>
<h5>location: только эти два файла, тот же php, что у остальных</h5>
<p><code>location =</code> с точным именем бьёт точнее regex. Обработчик php не выдумываю — копирую то, что уже работает для <code>*.php</code>. На Debian это часто <code>snippets/fastcgi-php.conf</code> плюс ваш <code>fastcgi_pass</code>. Сокет не копируйте из чужой статьи: у вас может быть другой пул.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="nginx">server {
    # ...

    location = /wp-login.php {
        limit_req zone=wplogin burst=4 nodelay;
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php-fpm.sock;  # как у вас в общем php
    }

    location = /xmlrpc.php {
        limit_req zone=xmlrpc burst=8 nodelay;
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php-fpm.sock;
    }

    # общий php — БЕЗ limit_req этих зон
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php-fpm.sock;
    }
}</pre>
<p><code>burst=4 nodelay</code> на логине: четыре лишних POST проходят сразу, дальше 429, без очереди. Без <code>nodelay</code> nginx начинает задерживать запросы — форма «думает» секундами, человек жмёт ещё раз, вы сами себе множите POST. Для логина я предпочитаю жёсткий отказ, не задержку.</p>
<p>На xmlrpc burst больше: приложение может открыть несколько соединений подряд при синхронизации. Если xmlrpc вам не нужен совсем — не лимитируйте, закройте:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="nginx">location = /xmlrpc.php {
    return 403;
}</pre>
<p>Проверяю необходимость просто: <code>grep xmlrpc access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head</code> и user-agent. Если неделю стучат только словари и <code>Hello world</code> — закрываю. Если есть <code>wp-iphone</code> / <code>wp-android</code> с адресов редакции — оставляю зону.</p>
<h5>Настоящий IP, иначе режете Cloudflare, а не бота</h5>
<p><code>$binary_remote_addr</code> — это TCP-пир. Стоит сайт за Cloudflare, nginx-прокси или балансировщиком хостера — пир один на всех. Лимит превращается в глобальный рубильник: первый бот выжигает burst, посетители с того же эджа ловят 429.</p>
<p>Смотрю, откуда приходит запрос:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">log_format debugip '$remote_addr cf="$http_cf_connecting_ip" xff="$http_x_forwarded_for"';</pre>
<p>Дальше <code>set_real_ip_from</code> на префиксы своего прокси и <code>real_ip_header</code> — тот заголовок, которому вы доверяете. Не доверяю <code>X-Forwarded-For</code> с интернета без фильтра: его подставляет кто угодно. Для Cloudflare — их диапазоны и <code>CF-Connecting-IP</code>. Для своего nginx-прокси — только его адрес в <code>set_real_ip_from</code>.</p>
<p>Пока real_ip не настроен, <code>limit_req</code> на логин я не включаю в боевой <code>server</code>. Иначе «защита» — это лотерея, кто первый постучал в этот воркер.</p>
<h5>Где лимит бесполезен: system.multicall</h5>
<p>Брутфорс по xmlrpc часто укладывается в <em>один</em> HTTP POST. Внутри <code>system.multicall</code> — сотни <code>wp.getUsersBlogs</code> с разными паролями. Для nginx это один запрос. Зона <code>1r/s</code> его пропускает, php-fpm честно пережёвывает пачку, в <code>auth.log</code> WordPress — сотни неудачных логинов, в access.log — одна строка 200.</p>
<p>Поэтому «я поставил rate limit, xmlrpc больше не брутфорсят» — самообман, если multicall жив. Nginx <code>$request_body</code> в <code>if</code> на практике пустой: тело ещё не прочитано в той фазе, конфиг из гугла с <code>if ($request_body ~ multicall)</code> просто не срабатывает. Я это не рисую как рабочую схему.</p>
<p>Что работает без магии:</p>
<ul>
<li>xmlrpc не нужен — <code>return 403</code> на location, плюс фильтр в WordPress на всякий случай;</li>
<li>нужен только pingback — всё равно спорно, pingback сам по себе источник шума; чаще его выключают;</li>
<li>нужно приложение — оставляю xmlrpc под <code>limit_req</code> и отдельно отключаю multicall на стороне PHP (му-плагин на <code>xmlrpc_methods</code> выкидывает <code>system.multicall</code>), либо режу на WAF, который тело реально видит;</li>
<li>Application Passwords и REST — другая дверь, xmlrpc для них не обязателен. Если мобильный вход уже через REST, xmlrpc я закрываю, не лимитирую.</li>
</ul>
<p>Лимит по запросам и разбор методов xmlrpc — два разных слоя. Первый без второго на этой двери недоделан.</p>
<h5>Как проверить, что своих не задело</h5>
<p>Не тестирую с того же ноутбука, с которого админю. Иначе сам себя выкину и буду чинить по аварийной консоли. С другого адреса, лучше с маленького VPS:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># форма должна открываться сколько угодно
for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-login.php; done

# POST — должны пойти 429 после burst
for i in $(seq 1 20); do
  curl -s -o /dev/null -w "%{http_code}\n" -X POST \
    -d "log=nosuchuser&amp;pwd=wrong&amp;wp-submit=Log+In" \
    https://example.com/wp-login.php
done

# ajax и главная не в этой зоне
for i in $(seq 1 30); do
  curl -s -o /dev/null -w "%{http_code}\n" -X POST https://example.com/wp-admin/admin-ajax.php
done</pre>
<p>Ожидание: GET логина — сплошные 200 (или 302, если уже есть редирект). POST — несколько 200/302, потом 429, не 503. ajax — без 429. Главная и карточки товара — без 429.</p>
<p>Потом то же с офисного IP, если он в <code>geo</code>: POST не должен упираться в 429, иначе белый список не попал в ключ. Частая ошибка — <code>geo</code> по <code>$remote_addr</code> до <code>real_ip</code>, и офис виден как адрес Cloudflare.</p>
<p>В логе ищу не «много 404», а явно статус и зону. С <code>limit_req_log_level warn</code> в error.log будет <code>limiting requests, excess: ... zone=wplogin</code>. Если зоны в логе нет, а 503 есть — вы смотрите не на лимитер, а на php-fpm/upstream.</p>
<h5>Что я не кладу в эти зоны</h5>
<ul>
<li><code>admin-ajax.php</code>, <code>async-upload.php</code>, cron <code>wp-cron.php</code> — это не брутфорс логина.</li>
<li>REST <code>/wp-json/</code> целиком. Там и легитимный редактор, и Application Passwords. Если резать — отдельные location на конкретные маршруты, не оптом.</li>
<li><code>wp-admin/</code> GET статики и <code>admin-post.php</code> с авторизованной сессией.</li>
<li>Статику и <code>wp-content/uploads</code> — к логину отношения не имеет.</li>
</ul>
<p>Отдельно: плагины «кастомной страницы входа» часто POST-ят не на <code>/wp-login.php</code>, а на <code>/</code> или на <code>/my-account/</code>. Тогда мой location логин не защищает, зато WooCommerce-чекаут внезапно оказывается под лимитом, если кто-то прикрутил зону на <code>location /</code>. Смотрю в access.log реальный URI при неудачном входе, не название плагина на коробке.</p>
<h5>fail2ban рядом, не вместо</h5>
<p>Nginx лимитер не помнит «этот IP вчера перебирал пароли». Он держит короткое окно. fail2ban по error.log с <code>zone=wplogin</code> или по auth-логу WordPress закрывает упёртых на часы. Но jail, который банит после трёх 429, снова отстрелит офис. Либо офис в ignoreip, либо порог заметно выше человеческого «забыл пароль, нажал пять раз».</p>
<p>Я не баняю 429 с ajax и не баняю 404 на <code>xmlrpc.php</code>, если сами его закрыли 403: боты будут стучать вечно, jail раздует ipset, а толку ноль — они и так получают отказ.</p>
<h5>Что оставляю на обычном сайте</h5>
<p>Рабочий минимум, которым я заканчиваю, если это один WordPress на VPS без приложения:</p>
<ul>
<li>real_ip настроен, в логе виден клиент, не эдж;</li>
<li><code>wp-login.php</code> — POST-only зона ~6r/m, burst 4 nodelay, статус 429;</li>
<li><code>xmlrpc.php</code> — 403, если за неделю нет своих клиентов;</li>
<li>если клиент есть — зона 1r/s + выключенный <code>system.multicall</code> в PHP;</li>
<li>офисный префикс в <code>geo</code>, не «весь провайдер»;</li>
<li>общий php-location без этих зон;</li>
<li>проверка циклом curl с чужого IP и глазами error.log, что падает зона, а не 503 от fpm.</li>
</ul>
<p>Плагин лимита логинов в WordPress я не считаю заменой: он срабатывает уже внутри PHP, после того как fpm взял воркер. Под словарём это дороже, чем отказ на nginx. Плагин имеет смысл как второй слой (капча, временная блокировка учётки), не как единственный.</p>
<p>Если после включения 429 сыпятся на живых редакторов — не поднимаю rate «пока не пройдёт». Сначала смотрю, какой URI и какой IP. Почти всегда это либо общий php-location, либо NAT офиса, либо xmlrpc приложения, которое я забыл, что оно существует.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/vebmasteru/nginx-limity-na-wp-login-i-xmlrpc-bez-otstrela-normalnyh-polzovatelej/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>journald съедает диск: SystemMaxUse, persistent vs volatile и что смотреть overnight</title>
		<link>https://remadmin.com/blog/linux/journald-sedaet-disk-systemmaxuse-persistent-vs-volatile-i-chto-smotret-overnight/</link>
					<comments>https://remadmin.com/blog/linux/journald-sedaet-disk-systemmaxuse-persistent-vs-volatile-i-chto-smotret-overnight/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Sun, 06 Sep 2026 12:55:16 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[journald]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[VPS]]></category>
		<category><![CDATA[логи]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7405</guid>

					<description><![CDATA[На маленьком VPS journald спокойно занимает гигабайты: Storage=auto плюс каталог /var/log/journal и лимит «10% файловой системы». Чем persistent отличается от volatile, как резать SystemMaxUse и что смотреть утром, если ночью диск дошёл до нуля.]]></description>
										<content:encoded><![CDATA[<p>Типичная ночная история на VPS на 20–40 гигабайт. Утром сайт лежит, SSH едва открывается, в логах приложения <code>No space left on device</code>. <code>df -h</code> показывает корневой раздел на 100%. Человек уже почистил <code>/tmp</code>, выкинул старые дампы, даже <code>apt clean</code> сделал — свободно 200 мегабайт, через час снова ноль.</p>
<p>Смотрю <code>du -xhd1 /var</code> и почти всегда в топе <code>/var/log</code>. Внутри него не <code>nginx/access.log</code> на первом месте, а <code>/var/log/journal</code> на гигабайты. Хостер предлагает «расширить диск». Расширять можно, но это не лечение: journald по умолчанию считает нормальным занять десятую часть файловой системы. На маленьком корне это уже не «логи», это треть полезного места.</p>
<p>Ниже — как я отличаю «журнал имеет право быть большим» от «он просто не ограничен», чем persistent отличается от volatile на практике, и что смотреть утром, если ночью диск дошёл до нуля. Без советов вида «поставьте плагин» и без удаления файлов наугад.</p>
<h5>Где journald вообще пишет, и почему каталог внезапно появляется</h5>
<p>У systemd-journald два реальных места на диске (точнее, на диске и в tmpfs):</p>
<ul>
<li><code>/run/log/journal</code> — volatile, живёт в памяти/tmpfs, после ребута пусто.</li>
<li><code>/var/log/journal</code> — persistent, переживает перезагрузку.</li>
</ul>
<p>Режим задаётся в <code>/etc/systemd/journald.conf</code> параметром <code>Storage=</code>. По умолчанию почти везде <code>auto</code>: если каталог <code>/var/log/journal</code> существует — пишем туда и храним между ребутами. Нет каталога — сидим в <code>/run</code>.</p>
<p>Это ловушка, на которую люди наступают сами. На «голом» облачном образе журнал был volatile, места хватало. Кто-то из документации скопировал <code>mkdir -p /var/log/journal</code> «чтобы логи не терялись», перезапустил <code>systemd-journald</code> — и <code>auto</code> молча стал persistent. Лимиты при этом никто не ставил. Через неделю диск красный.</p>
<p>Проверяю так:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">df -h / /var /run
du -sh /var/log/journal /run/log/journal /var/log 2&gt;/dev/null
journalctl --disk-usage
ls -ld /var/log/journal
systemd-analyze cat-config systemd/journald.conf</pre>
<p><code>journalctl --disk-usage</code> говорит, сколько journald сам считает занятым. <code>du</code> — сколько видит файловая система. Если они сильно расходятся, смотрю разреженные файлы и «хвосты» после неудачного vacuum: <code>du -sh --apparent-size /var/log/journal</code> рядом с обычным <code>du -sh</code>.</p>
<h5>Почему «дефолт 10%» на VPS — уже инцидент</h5>
<p>В man у journald это звучит безобидно: <code>SystemMaxUse</code> по умолчанию — около 10% файловой системы, с потолком 4G; <code>SystemKeepFree</code> — около 15% свободными. На корне в 40G это до четырёх гигабайт журнала. На корне в 20G — те же проценты, только больнее.</p>
<p>Четыре гигабайта логов на виртуалке, где сам сайт весит 800 мегабайт, — не «запас на разбор». Это место, которое ночью отъест MySQL, очередь почты или бэкап.</p>
<p>Важная оговорка: лимиты — не жёсткая квота в момент записи каждой строки. Journald ротирует и чистит архивные файлы, когда доходит до порога. Активный <code>system.journal</code> так просто не схлопнется. Если диск уже 100%, vacuum может не стартовать нормально, потому что некуда дописать ротацию. Классический труп: раздел полный, journald не вакуумит, сервисы не пишут, SSH на ключах ещё жив, потому что сессия старая.</p>
<p>Поэтому на маленьком VPS я не оставляю дефолт. Дефолт рассчитан на машину, где <code>/var</code> не равен всему диску, и где гигабайды логов — нормальная цена за историю.</p>
<h5>Persistent vs volatile: это про утро после ребута, не про вкус</h5>
<p>Volatile соблазнительно прост: ребутнул — логи кончились, диск не раздувается. Я так оставляю только там, где журнал всё равно уезжает на внешний syslog и локальная история после ребута не нужна. На обычном VPS с сайтом это почти никогда не тот случай.</p>
<p>Типичный ночной сценарий: в 03:14 гипервизор переехал, ядро поймало OOM, сработал watchdog хостера, или сам <code>systemd</code> ушёл в reboot после failed unit. Утром вам нужен предыдущий бут. Он берётся так:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">journalctl --list-boots
journalctl -b -1 -p err..alert --no-pager
journalctl -b -1 -u ssh -u nginx -u php-fpm -u mysql --no-pager | tail -n 200</pre>
<p>Если <code>Storage=volatile</code>, после ребута <code>-b -1</code> пустой. Вы будете гадать по <code>last reboot</code> и по графику load у хостера. Это не диагностика, это гадание.</p>
<p>Persistent без лимита — другая крайность: история есть, но её некуда писать, потому что диск кончился самой историей.</p>
<p>Рабочая схема на VPS, которую я ставлю почти всегда: persistent, но маленький. История на несколько дней, не на квартал. Внешний syslog — если он уже есть; не вместо локального журнала, а поверх, чтобы переживать смерть самой машины.</p>
<h5>Что смотреть overnight, пока диск ещё не ноль</h5>
<p>Если диск уже 100% — сначала воздух, потом разбор. Если ещё есть 1–2 гигабайта, ночью я смотрю не «красивый dashboard», а четыре вещи.</p>
<p>Первое: это вообще journald или рядом лежит второй обжора. Часто параллельно толстеют <code>/var/log</code> от rsyslog, бинарные логи MySQL, <code>json-file</code> логи Docker. Journald виноватят первым, потому что путь незнакомый.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">du -xhd1 /var /var/log /var/lib 2&gt;/dev/null | sort -h
du -sh /var/lib/docker/containers 2&gt;/dev/null
du -sh /var/lib/mysql 2&gt;/dev/null
ls -lh /var/log/*.log /var/log/syslog /var/log/messages 2&gt;/dev/null</pre>
<p>Второе: журнал вырос за ночь или копился две недели. Смотрю даты файлов и размер активного относительно архивных:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ls -lhS /var/log/journal/*/* 2&gt;/dev/null | head
journalctl --header | head -n 40</pre>
<p>Архивные файлы с <code>@</code> в имени — уже закрытые куски. Если за ночь вырос один свежий файл на гигабайт — это не «дефолтный 10%», это болтливый юнит. Если лежат десятки старых файлов по 300–400M — это никто никогда не ставил <code>SystemMaxUse</code> и не делал vacuum.</p>
<p>Третье: был ли ребут. <code>journalctl --list-boots</code>, <code>who -b</code>, <code>last -x reboot | head</code>. Если бут ночью был, первым делом <code>journalctl -b -1 -p err</code>, а не сегодняшний хвост. Сегодняшний хвост после ребута начинается с «диска мало» и маскирует причину.</p>
<p>Четвёртое: не забился ли <code>/run</code>. Volatile журнал живёт в tmpfs. На машине с 1–2 гигабайтами RAM <code>/run</code> маленький. Когда он полный, симптомы странные: не стартуют юниты, <code>Failed to create ... No space left</code>, при этом <code>df -h /</code> ещё живой. Смотрю <code>df -h /run</code> отдельно, всегда.</p>
<h5>Кто орёт в журнал: без этого лимит только отодвинет взрыв</h5>
<p>Ограничить размер — обязательно. Но если каждую ночь один сервис пишет сотни тысяч строк, вы просто быстрее упрётесь в потолок и начнёте терять именно те сообщения, которые нужны для разбора.</p>
<p>Счётчик по юнитам за ночь, без jq:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">journalctl --since "6 hours ago" -o json --no-pager | python3 -c '
import sys, json, collections
c = collections.Counter()
for line in sys.stdin:
    try:
        j = json.loads(line)
    except Exception:
        continue
    key = j.get("_SYSTEMD_UNIT") or j.get("SYSLOG_IDENTIFIER") or "?"
    c[key] += 1
for k, v in c.most_common(20):
    print("%8d  %s" % (v, k))
'</pre>
<p>На живой машине это может минуту помолчать — json тяжёлый. Если python3 жалко, грубее по short-формату:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">journalctl --since "6 hours ago" --no-pager | awk '{print $5}' | sed 's/\[.*//' | sort | uniq -c | sort -nr | head</pre>
<p>Пятое поле в short — не священная корова, на неанглийской локали колонки плывут. Для разбора лучше json.</p>
<p>Что обычно оказывается наверху, когда «за ночь съело диск»:</p>
<ul>
<li>systemd-таймер бэкапа с <code>StandardOutput=journal</code> и <code>rsync -v</code> / <code>find</code> на сотни тысяч файлов;</li>
<li>fail2ban + ssh-парольный брут: одна попытка — несколько строк в journal и ещё дубль в auth.log;</li>
<li>PHP/Node в debug, который кто-то включил «на час» в пятницу;</li>
<li>сетевой юнит, который рестартует в цикле и каждый раз пишет stack trace;</li>
<li>ядро и OOM-killer — это уже не болтовня, это авария, её резать лимитом журнала нельзя, её надо читать.</li>
</ul>
<p>Для шумного, но не аварийного юнита правильнее прикрутить его вывод, а не бесконечно растить journal. В unit-файле: <code>StandardOutput=append:/var/log/backup.log</code> плюс logrotate, или убрать <code>-v</code>. RateLimit в journald.conf режет пачку одинаковых сообщений, но на бэкап с уникальными путями в каждой строке он почти не действует: burst исчерпывается, а диск всё равно пишется, пока не упрётся.</p>
<h5>SystemMaxUse и соседние крутилки</h5>
<p>Править лучше drop-in, а не жирный коммент в исходном <code>journald.conf</code>. Так видно, что меняли вы, а не пакет.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">mkdir -p /etc/systemd/journald.conf.d
cat &gt;/etc/systemd/journald.conf.d/size.conf &lt;&lt;'EOF'
[Journal]
Storage=persistent
SystemMaxUse=200M
SystemKeepFree=1G
SystemMaxFileSize=50M
RuntimeMaxUse=50M
MaxRetentionSec=7day
MaxFileSec=1day
Compress=yes
EOF
systemctl restart systemd-journald
journalctl --vacuum-size=200M --vacuum-time=7d
journalctl --disk-usage</pre>
<p>Зачем каждая строка, коротко.</p>
<p><code>Storage=persistent</code> — явно, чтобы не зависеть от «кто-то случайно создал каталог». Каталог после этого должен существовать: <code>mkdir -p /var/log/journal</code>, права оставить journald самому поправить.</p>
<p><code>SystemMaxUse=200M</code> — потолок persistent-журнала. На VPS с сайтом и ssh мне для разбора инцидента обычно хватает десятков–пары сотен мегабайт, не гигабайт. Если это шлюз, с которого разбирают неделю брутфорса по сырым логам — поставьте больше, но цифрой, не дефолтом.</p>
<p><code>SystemKeepFree=1G</code> — не съедать последний гигабайт, даже если MaxUse ещё не достигнут. На маленьком корне это важнее MaxUse: MySQL и очередь почты должны иметь куда писать.</p>
<p><code>SystemMaxFileSize</code> — чтобы один файл не вырос в весь лимит. Vacuum чистит архивные; огромный активный файл неудобно и vacuum, и копировать.</p>
<p><code>RuntimeMaxUse</code> — потолок для <code>/run</code>, чтобы volatile не убил tmpfs.</p>
<p><code>MaxRetentionSec=7day</code> — дефолт по времени почти не режет, живёт размер. Я хочу неделю, не «пока влезает 10% диска». Для разбора overnight недели хватает. Месяц уже про хранение, его лучше делать на syslog-коллекторе, не на корне VPS.</p>
<p>После рестарта journald файлы сами не схлопнутся до новой цифры в ту же секунду. Поэтому сразу <code>--vacuum-size</code> / <code>--vacuum-time</code>. Рестарт журналы не удаляет — это не <code>rm</code>.</p>
<h5>Vacuum — не rm -rf, особенно когда диск уже 100%</h5>
<p>Нормальный путь:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">journalctl --disk-usage
journalctl --vacuum-size=100M
journalctl --vacuum-time=2d
journalctl --verify</pre>
<p><code>--verify</code> иногда находит битый хвост после внезапного отключения питания. Битый файл vacuum может обходить стороной, и место не отдаётся. Тогда смотрю имена в <code>/var/log/journal/&lt;machine-id&gt;/</code> и убираю архивные <code>*@*.journal</code> / <code>*.journal~</code>, не трогая текущий <code>system.journal</code>.</p>
<p>Чего не делаю, пока есть другой выход: <code>rm -rf /var/log/journal/*</code> на живой системе. Один раз из ста это проходит, в остальные journald остаётся с открытыми inode, место не возвращается, а после рестарта вы получаете пустой журнал ровно в тот момент, когда хотели понять, почему диск кончился.</p>
<p>Аварийный порядок, если писать уже некуда:</p>
<ul>
<li>найти и удалить пару самых старых архивных файлов <code>*@*.journal</code> — это даёт воздух;</li>
<li>сразу <code>journalctl --vacuum-size=80M</code>;</li>
<li>поставить drop-in с лимитами и перезапустить journald;</li>
<li>только потом искать болтливый юнит, иначе пока вы читаете json, диск снова забьётся.</li>
</ul>
<p>Сигнал «почистись прямо сейчас» у journald — <code>SIGUSR2</code>. <code>SIGUSR1</code> — сбросить буфер на диск, не vacuum. Путать их бессмысленно, но люди путают.</p>
<h5>Двойной учёт: journald, rsyslog и Docker</h5>
<p>На Debian/Ubuntu часто живы оба мира. journald пишет своё. rsyslog забирает через imjournal или сокет и пишет <code>/var/log/syslog</code>, <code>/var/log/auth.log</code>. Итого одна и та же неудачная попытка SSH лежит дважды. Rate-limit в journald на файлы rsyslog не действует.</p>
<p>Проверяю, не тащим ли мы дубль:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">systemctl is-active rsyslog syslog-ng 2&gt;/dev/null
grep -RInE 'imjournal|imuxsock|ForwardToSyslog' /etc/rsyslog.conf /etc/rsyslog.d /etc/systemd/journald.conf /etc/systemd/journald.conf.d 2&gt;/dev/null
du -sh /var/log/journal /var/log/syslog /var/log/auth.log 2&gt;/dev/null</pre>
<p><code>ForwardToSyslog=yes</code> плюс persistent journald — сознательное удвоение. Имеет смысл, если внешний разбор завязан на syslog-файлы, а journal вы хотите маленьким. Не имеет смысла, если никто эти файлы не читает, а logrotate на syslog стоит «weekly, rotate 8» и молча держит ещё сотни мегабайт.</p>
<p>Docker — отдельная яма. <code>json-file</code> драйвер пишет в <code>/var/lib/docker/containers/.../*.log</code>, это не journald. Человек чистит journal, диск не отходит, начинается шаманство. Если в <code>du</code> победил docker — лимиты в <code>daemon.json</code> (<code>log-opts max-size</code>), не в journald.conf. Путь другой, болезнь похожая.</p>
<h5>Что я реально оставляю на маленьком VPS</h5>
<p>Для обычного сайта (nginx, php-fpm, mysql, ssh) на одном диске:</p>
<ul>
<li>persistent, 150–300M потолок, неделя хранения;</li>
<li><code>/run</code> ограничен десятками мегабайт;</li>
<li>таймеры бэкапа не пишут verbose в journal;</li>
<li>если rsyslog нужен файлами для fail2ban — пусть живёт, но logrotate жёсткий, не дефолтный weekly на 100M файлах;</li>
<li>внешний syslog — когда машин больше двух или когда хочется историю длиннее недели.</li>
</ul>
<p>Volatile я ставлю на одноразовые тестовые машины и на те VPS, где журнал и так уезжает агентом, а локальный диск маленький и ребут не является загадкой.</p>
<p>И я не храню journal как бэкап логов. Это кольцевой буфер для разбора. Если лог должен жить три месяца «по требованиям», ему место не в <code>/var/log/journal</code> на корне.</p>
<h5>Утренний чеклист, если ночью диск снова опух</h5>
<p>Короткий порядок, которым я хожу, чтобы не начать не с того конца:</p>
<ul>
<li><code>df -h / /run /var</code> — какой именно раздел, не «диск вообще»;</li>
<li><code>du -xhd1 /var</code> — journal это или mysql/docker/бэкап;</li>
<li>воздух: vacuum или пара архивных файлов, не apt и не <code>/tmp</code>;</li>
<li><code>journalctl --list-boots</code> и при ночном ребуте сразу <code>-b -1 -p err</code>;</li>
<li>счётчик юнитов за ночь — кто писал, не «сколько процентов в SystemMaxUse»;</li>
<li>drop-in с <code>SystemMaxUse</code> / <code>SystemKeepFree</code>, vacuum до новой цифры;</li>
<li>проверка, что через час <code>journalctl --disk-usage</code> не ползёт обратно с той же скоростью.</li>
</ul>
<p>Если после лимита журнал упирается в потолок каждые несколько часов — лимит сработал, но источник шума жив. Тогда это уже не настройка journald, а разбор конкретного юнита. Крутить <code>SystemMaxUse</code> вверх, «чтобы влезало», на маленьком VPS я не соглашаюсь: вы просто вернёте ту же заявку через неделю, только с более толстым журналом и снова нулём на диске.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/linux/journald-sedaet-disk-systemmaxuse-persistent-vs-volatile-i-chto-smotret-overnight/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>WP2Shell — критическая уязвимость в ядре WordPress (CVE-2026-63030)</title>
		<link>https://remadmin.com/blog/vebmasteru/wp2shell-kriticheskaya-uyazvimost-v-yadre-wordpress-cve-2026-63030/</link>
					<comments>https://remadmin.com/blog/vebmasteru/wp2shell-kriticheskaya-uyazvimost-v-yadre-wordpress-cve-2026-63030/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Mon, 31 Aug 2026 23:34:25 +0000</pubDate>
				<category><![CDATA[Вебмастеру]]></category>
		<category><![CDATA[CVE-2026-64638]]></category>
		<category><![CDATA[Wordpress]]></category>
		<category><![CDATA[XSS2Shell]]></category>
		<category><![CDATA[безопасность]]></category>
		<category><![CDATA[Уязвимость]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7399</guid>

					<description><![CDATA[Pre-auth RCE в WordPress: batch REST API, SQLi, создание левого администратора и загрузка шелла через плагин. Кого касается wp2shell и что делать.]]></description>
										<content:encoded><![CDATA[<p>В середине июля 2026 года в ядре WordPress нашли критическую дыру, которую быстро окрестили <strong>wp2shell</strong>. Это не плагин и не тема — баг в самом WordPress. Анонимный злоумышленник без логина и пароля мог через REST API добраться до SQL-инъекции, создать в базе нового администратора, зайти в админку и загрузить вредоносный плагин с PHP-шеллом. Звучит как страшилка, но цепочку уже эксплуатировали в дикой природе — массовые POST-запросы на <code>/wp-json/batch/v1</code> пошли буквально на следующий день после disclosure.</p>
<p>Если вы пропустили новость или думаете «автообновление само всё поставило» — всё равно стоит проверить версию и пользователей. Патч вышел 17 июля в <strong>6.9.5</strong> и <strong>7.0.2</strong> (для ветки 6.8.x — <strong>6.8.6</strong>, там закрыли только SQLi-компонент). WordPress даже включил принудительное автообновление — но на VPS, мультисайтах и «забытых» лендингах дыра нередко висит до сих пор.</p>
<h5>Это не XSS2Shell</h5>
<p>Чтобы не путаться: в начале августа вышел отдельный баг <strong>XSS2Shell</strong> (CVE-2026-64638) — reflected XSS на <code>wp-login.php</code>, патч в 7.0.3. Там другой вход и нужен залогиненный админ, которого уводят на фишинговую страницу. А wp2shell — полностью без авторизации, через batch-endpoint. В статьях и Medium их часто мешают в одну кучу, хотя это две разные истории. Ниже — про wp2shell.</p>
<h5>В чём суть: две дыры в одной цепочке</h5>
<p><strong>CVE-2026-63030</strong> — путаница маршрутов в batch-обработчике REST API (<code>WP_REST_Server::serve_batch_request_v1</code>). Batch-endpoint (<code>/wp-json/batch/v1</code>, а без ЧПУ — <code>/?rest_route=/batch/v1</code>) позволяет отправить несколько подзапросов одним POST. Если первый подзапрос падает с ошибкой при разборе URL, WordPress кладёт ошибку в один массив, но не синхронизирует второй. Индексы съезжают — и следующий подзапрос атакующего обрабатывается с чужими правами, мимо проверки «можно ли это без логина».</p>
<p><strong>CVE-2026-60137</strong> — SQL-инъекция в параметре <code>author__not_in</code> (в REST он приходит как <code>author_exclude</code>). Сама по себе она за auth-блоком. Но batch-баг протаскивает туда анонимный запрос. Инъекция слепая — в ответе данных нет, зато работает time-based oracle через <code>SLEEP()</code>.</p>
<p>Одна без другой до полного RCE на типичном сайте не доводит. Вместе — критическая цепочка с CVSS 9.8.</p>
<h5>Как выглядит атака: левый админ → плагин → шелл</h5>
<p>Упрощённо, как описывают исследователи (Searchlight Cyber, публичные PoC вроде wp2shell на GitHub) и что видно в логах реальных инцидентов:</p>
<ol>
<li>Анонимный POST на <code>/wp-json/batch/v1</code> с «кривым» batch-envelope — срабатывает route confusion.</li>
<li>В том же batch — запрос с poisoned <code>author_exclude</code>, SQLi читает/меняет данные в БД (часто через blind SQLi и манипуляции с object cache / changeset — детали в PoC различаются).</li>
<li>Через REST создаётся <strong>новый пользователь с ролью administrator</strong> — логин обычно вида <code>wp2_*</code>, email иногда на подставных доменах вроде <code>@wordpress-svc.internal</code>. Brute bcrypt-хеша существующего админа для этого не нужен — аккаунт поднимают «с нуля».</li>
<li>Атакующий <strong>логинится</strong> под этой учёткой (в логах потом видны нормальные запросы в <code>wp-admin</code> с валидным nonce).</li>
<li>Через <strong>Загрузку плагина</strong> (<code>update.php?action=upload-plugin</code>) заливается ZIP с PHP — имена в инцидентах разные: <code>media-optimization-core-*</code>, <code>sgio-wp2shell-*</code> и т.п. Активировать плагин в списке не обязательно — PHP в <code>wp-content/plugins/</code> часто доступен по URL напрямую.</li>
<li>Дальше — <code>id</code>, <code>uname</code>, выкачка <code>wp-config.php</code>, спам-рассылки, бэкдоры в <code>mu-plugins</code>, cron, переименование Wordfence.</li>
</ol>
<p>Именно этот сценарий — «создали админа, зашли, залили шелл через плагины» — описан в публичных разборах wp2shell, в том числе на Medium. Не социнженерия и не «админ кликнул фишинг».</p>
<h5>Кого касается</h5>
<p>Полная цепочка до RCE:</p>
<ul>
<li>WordPress <strong>6.9.0 – 6.9.4</strong></li>
<li>WordPress <strong>7.0.0 – 7.0.1</strong></li>
</ul>
<p>Только SQLi (без batch-RCE, но всё равно надо патчить): <strong>6.8.0 – 6.8.5</strong> → фикс в <strong>6.8.6</strong>.</p>
<p>Версии ниже 6.8 batch-механизм в том виде, в каком его эксплуатируют, не затрагивают — но это не повод радоваться, если у вас вообще древний WP.</p>
<p>Нюанс из advisory: полный RCE-chain на практике чаще срабатывает, когда <strong>нет внешнего persistent object cache</strong> (Redis/Memcached). На обычном shared-хостинге это как раз большинство сайтов.</p>
<h5>Как понять, что уже взломали</h5>
<p>Картина похожа на массовые взломы через Elementor или Битрикс — только вход другой.</p>
<ul>
<li>Незнакомые администраторы в «Пользователи» — логины <code>wp2_*</code>, <code>wpsvc_*</code>, странные email.</li>
<li>Новые папки в <code>wp-content/plugins/</code> с «оптимизационными» или «сервисными» названиями и свежей датой.</li>
<li>В access-логах — массовые POST на <code>batch/v1</code>, ответы <code>207 Multi-Status</code>, User-Agent вроде <code>wp2shell-rce/1.0</code>.</li>
<li>После входа «левого» админа — запросы на <code>upload-plugin</code>, активация плагинов, дефейс главной.</li>
<li>Пersistence: cron у пользователя веб-сервера, записи в <code>wp_options</code>, must-use plugins в <code>wp-content/mu-plugins/</code>.</li>
</ul>
<p>Из разбора реального инцидента (deface «Hacked by CoupDeGrace»): сайт обновили до 7.0.2, но бэкдоры продолжали работать — <strong>патч закрывает вход, но не чистит сервер</strong>. Как с Битриксом: обновили CMS, а webshell из прошлого взлома остался.</p>
<h5>Что делать</h5>
<p><strong>1. Обновить ядро.</strong> Единственный нормальный способ, как разработчики и задумывали: <strong>6.9.5</strong>, <strong>7.0.2</strong> или новее (на сегодня логично сразу до актуального security-release — 7.0.4 и аналогов в своей ветке). Анонс: <a href="https://wordpress.org/news/2026/07/wordpress-7-0-2-security-release/" target="_blank" rel="noopener nofollow">WordPress 7.0.2 Security Release</a>. Advisory: <a href="https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-ff9f-jf42-662q" target="_blank" rel="noopener nofollow">GHSA batch-route confusion</a> и <a href="https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-fpp7-x2x2-2mjf" target="_blank" rel="noopener nofollow">GHSA SQL injection</a>.</p>
<p><strong>2. Убедиться, что автообновление реально сработало.</strong> Зайдите в Консоль → Обновления и посмотрите версию. «Должно было само» ≠ «обновилось».</p>
<p><strong>3. Проверить пользователей.</strong> Удалите всех незнакомых админов. Смените пароли нормальным администраторам, отзовите Application Passwords.</p>
<p><strong>4. Пройтись по файлам.</strong> <code>plugins/</code>, <code>mu-plugins/</code>, <code>uploads/</code>, лишние <code>.php</code> в корне. Сравните с чистым архивом той же версии WP, если есть сомнения в целостности ядра.</p>
<p><strong>5. Посмотреть логи.</strong> POST на <code>batch/v1</code> с июля 2026 — повод считать сайт скомпрометированным, даже если визуально всё чисто.</p>
<p><strong>6. Временная мера, если обновиться прямо сейчас нельзя:</strong> заблокировать на WAF/nginx <code>/wp-json/batch/v1</code> и <code>?rest_route=/batch/v1</code> для анонимов. Как временный запрет POST на отдельные файлы у Битрикса — не 100%, но лучше, чем ничего.</p>
<p><strong>Важно:</strong> если взлом подтвердился — сначала чистка и ротация секретов (БД, FTP, SMTP), потом спокойное обновление. Восстановление из бэкапа только если копия точно старше первого POST на batch и гарантированно чистая.</p>
<h5>UPD — август: XSS2Shell и Imagick RCE</h5>
<p>После wp2shell WordPress выпустил ещё security-релизы:</p>
<ul>
<li><strong>7.0.3</strong> (6 августа) — XSS2Shell, CVE-2026-64638, XSS на экране входа с цепочкой через Application Password (другой сценарий, нужен фишинг админа).</li>
<li><strong>7.0.4</strong> (12 августа) — CVE-2026-65640, RCE через загрузку Postscript при Imagick + Ghostscript, нужен пользователь с <code>upload_files</code>.</li>
</ul>
<p>Если вы на 7.0.2 после wp2shell — докатите до актуального патча своей ветки. Это уже не wp2shell, но дыры тоже серьёзные.</p>
<p>Нужна помощь — проверить версию, найти левых админов и шеллы, обновить WordPress под ключ — пишите через <a href="https://remadmin.com/podderzhka-sajtov/">контакты на сайте</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/vebmasteru/wp2shell-kriticheskaya-uyazvimost-v-yadre-wordpress-cve-2026-63030/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ILSERBY — мой каталог сетевых инструментов в одном месте</title>
		<link>https://remadmin.com/blog/vebmasteru/ilserby-moj-katalog-setevyh-instrumentov-v-odnom-meste/</link>
					<comments>https://remadmin.com/blog/vebmasteru/ilserby-moj-katalog-setevyh-instrumentov-v-odnom-meste/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Mon, 31 Aug 2026 14:10:58 +0000</pubDate>
				<category><![CDATA[Вебмастеру]]></category>
		<category><![CDATA[DNS]]></category>
		<category><![CDATA[ilserby]]></category>
		<category><![CDATA[SSL]]></category>
		<category><![CDATA[онлайн-инструменты]]></category>
		<category><![CDATA[сеть]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7394</guid>

					<description><![CDATA[Зачем я собрал ilserby.net: один каталог онлайн-инструментов вместо десятка закладок. Обзор сетевых проверок — DNS, WHOIS, SSL, IP, прокси и не только.]]></description>
										<content:encoded><![CDATA[<p>Часто в работе нужны мелкие онлайн-штуки: глянуть DNS, проверить сертификат, пробить IP, сравнить подсети. Каждый раз — новая вкладка, другой сайт, другой интерфейс, иногда ещё и регистрация «на пробу». Со временем закладок становится слишком много, а половина из них я открываю раз в полгода и каждый раз вспоминаю заново, где именно лежит нужная проверка.</p>
<p>Решил собрать всё в одном месте — <a href="https://ilserby.net/" target="_blank" rel="noopener nofollow">ilserby.net</a>. Это мой каталог сетевых и dev-инструментов: без аккаунта, бесплатно, с нормальным русским/английским интерфейсом и понятным результатом, а не просто сырой дамп. Ниже — несколько штук из сетевого блока, которыми пользуюсь чаще всего.</p>
<h5>Домены и DNS</h5>
<p><strong><a href="https://ilserby.net/whois" target="_blank" rel="noopener nofollow">WHOIS / RDAP Lookup</a></strong> — когда нужно быстро понять, кто владелец домена, когда истекает регистрация и какие NS стоят. Тянет и RDAP, и классический WHOIS, если для зоны так принято.</p>
<p><strong><a href="https://ilserby.net/dns" target="_blank" rel="noopener nofollow">DNS Lookup</a></strong> — A, AAAA, MX, TXT, CNAME, SOA, CAA, плюс DNSSEC и проверка распространения A-записей через публичные резолверы. Удобно после смены хостинга или почты, когда «вроде всё прописал, но где-то ещё старое».</p>
<p><strong><a href="https://ilserby.net/email-auth" target="_blank" rel="noopener nofollow">Email Authentication</a></strong> — SPF, DMARC, DKIM и MX в одном заходе. Перед тем как жаловаться на спам или недоставку писем, я обычно начинаю отсюда: видно, что реально опубликовано в DNS, а не что «должно быть по инструкции провайдера».</p>
<p><strong><a href="https://ilserby.net/lookalike" target="_blank" rel="noopener nofollow">Lookalike Domains</a></strong> — генератор похожих доменов с homoglyph и punycode. Не замена мониторинга бренда, но для осознания, как может выглядеть фишинговая ссылка «почти как ваш», — полезная штука.</p>
<h5>IP, прокси и репутация</h5>
<p><strong><a href="https://ilserby.net/ip" target="_blank" rel="noopener nofollow">IP Lookup</a></strong> — reverse DNS, гео, ASN, сеть и RDAP по публичному адресу. Часто хватает одного экрана, чтобы понять «это хостинг, CDN или что-то странное».</p>
<p><strong><a href="https://ilserby.net/ip-blacklist" target="_blank" rel="noopener nofollow">IP Blacklist Check</a></strong> — проверка по DNSBL/RBL: спам, abuse, брутфорс. Если с сервера перестали принимать почту или API начали резать по IP — смотрю сюда в первую очередь.</p>
<p><strong><a href="https://ilserby.net/proxy-check" target="_blank" rel="noopener nofollow">Proxy Checker</a></strong> — до 50 прокси за раз, HTTP/HTTPS/SOCKS4/SOCKS5, с exit IP, гео и временем ответа. Результаты приходят по одному, можно остановить очередь и выгрузить только рабочие.</p>
<h5>HTTPS, заголовки и безопасность</h5>
<p><strong><a href="https://ilserby.net/ssl" target="_blank" rel="noopener nofollow">SSL Checker</a></strong> — срок действия, цепочка, SAN, hostname match и типичные косяки установки. Перед продлением Let&#8217;s Encrypt или после переезда на новый сервер — must have.</p>
<p><strong><a href="https://ilserby.net/headers" target="_blank" rel="noopener nofollow">HTTP Headers</a></strong> — цепочка редиректов, security headers, robots.txt и sitemap. Быстрый аудит «как отвечает главная» без curl в терминале.</p>
<p><strong><a href="https://ilserby.net/fingerprint" target="_blank" rel="noopener nofollow">Browser Fingerprint</a></strong> — canvas, WebGL, шрифты, Client Hints и композитный ID. Всё считается локально в браузере, никуда не уходит. Полезно, когда тестируешь приватность или сравниваешь профили.</p>
<h5>Подсети, сертификаты и мелкий sysadmin</h5>
<p><strong><a href="https://ilserby.net/subnet-calculator" target="_blank" rel="noopener nofollow">IPv4 Subnet Calculator</a></strong> — network/broadcast, маска, wildcard, диапазон хостов из CIDR. Планирую firewall или VPC — не считаю в голове.</p>
<p><strong><a href="https://ilserby.net/cidr-overlap" target="_blank" rel="noopener nofollow">CIDR Overlap &amp; Contains</a></strong> — пересекаются ли две подсети и входит ли IP в блок. Спасает от классической ошибки «прокинули peering, а маршруты бьются».</p>
<p><strong><a href="https://ilserby.net/cert-generator" target="_blank" rel="noopener nofollow">Self-Signed Certificate Generator</a></strong> — ключ и самоподписанный сертификат прямо в браузере, с SAN. Для dev/staging, не для публичного продакшена.</p>
<p><strong><a href="https://ilserby.net/ssh-key-inspector" target="_blank" rel="noopener nofollow">OpenSSH Public Key Inspector</a></strong> — разбор .pub и целого authorized_keys: тип, SHA256/MD5, дубликаты. Быстрее, чем гонять ssh-keygen по каждой строке.</p>
<p><strong><a href="https://ilserby.net/nginx-htaccess" target="_blank" rel="noopener nofollow">Nginx <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> .htaccess Converter</a></strong> — черновой перевод редиректов и rewrite между nginx и Apache. Не волшебная кнопка, но стартовая точка при миграции — норм.</p>
<p><strong><a href="https://ilserby.net/sla-calculator" target="_blank" rel="noopener nofollow">SLA &amp; Uptime Calculator</a></strong> — перевод 99.9 / 99.99 в минуты простоя за день, месяц, год и обратно. Удобно для договоров и postmortem после инцидента.</p>
<h5>Зачем вообще свой каталог</h5>
<p>Не претендую, что ilserby заменит специализированные сервисы уровня Shodan или полноценный мониторинг. Идея проще: <em>один вход</em>, одинаковый UX, инструменты, которые реально нужны в повседневной админке и вебмастерской рутине. Плюс там же лежат конвертеры, генераторы и SEO-штуки — но сетевой блок я использую чаще всего.</p>
<p>Каталог буду допиливать и расширять: новые проверки, мелкие улучшения интерфейса, то, что сам всплывает в работе и бесит на чужих сайтах. Цель простая — чтобы в итоге получился самый удобный для меня (и не только) набор онлайн-инструментов в одном месте. Заглядывайте на <a href="https://ilserby.net/" target="_blank" rel="noopener nofollow">ilserby.net</a> — и если чего-то не хватает, пишите, что добавить в первую очередь.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/vebmasteru/ilserby-moj-katalog-setevyh-instrumentov-v-odnom-meste/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Инкрементальный бэкап через rsync &#8211;link-dest: полные слепки, hardlink и проверка restore</title>
		<link>https://remadmin.com/blog/linux/inkrementalnyj-bekap-cherez-rsync-link-dest-polnye-slepki-hardlink-i-proverka-restore/</link>
					<comments>https://remadmin.com/blog/linux/inkrementalnyj-bekap-cherez-rsync-link-dest-polnye-slepki-hardlink-i-proverka-restore/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Sun, 30 Aug 2026 01:08:35 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[hardlink]]></category>
		<category><![CDATA[rsync]]></category>
		<category><![CDATA[Резервное копирование]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7391</guid>

					<description><![CDATA[Один каталог с --delete не даёт вчерашний файл, полный tar каждую ночь жрёт диск. rsync --link-dest делает слепки, которые выглядят полными, а на диске лежат только изменения. Как собрать, где ломается hardlink и как проверить restore не по коду выхода.]]></description>
										<content:encoded><![CDATA[<p>Типичная схема «бэкапа», которую я застаю на VPS: один каталог, в него каждую ночь <code>rsync -a --delete</code> с продакшена. Места мало, скорость нормальная, откатить вчерашний файл нельзя — его уже перетёрли сегодняшним. Второй край: каждый день полный <code>tar</code> в новый файл. Через две недели диск кончился, а проверять restore никто не пробовал, потому что «архив же пишется, код нулевой».</p>
<p><code>--link-dest</code> лежит между этими двумя дуростями. Каждый слепок выглядит как полный каталог: зашёл в <code>/backup/site/2026-08-27/</code> — видишь весь сайт, как в тот день. На диске при этом лежат только изменившиеся файлы. Неизменные — hardlink на inode прошлого слепка. Это не магия файловой системы и не «инкремент в проприетарном формате». Это обычный ext4/xfs и обычный rsync. Именно поэтому я его до сих пор ставлю там, где не нужен Borg/restic со своими репозиториями, а нужен каталог, из которого файл можно просто скопировать.</p>
<p>Ниже — как я это собираю, где оно ломается, и как проверить restore так, чтобы не узнать о битом бэкапе в день аварии.</p>
<h5>Что именно делает &#8211;link-dest, без сказки про «инкремент»</h5>
<p>Rsync сравнивает источник с каталогом назначения. Если файла в назначении ещё нет, он смотрит в каталог из <code>--link-dest</code>. Файл там есть, размер и mtime совпали — в новый слепок кладётся hardlink, байты заново не копируются. Файл изменился — в новый слепок пишется новая копия, старый inode остаётся в старом слепке.</p>
<p>Поэтому:</p>
<ul>
<li>каждый дневной каталог самодостаточный: <code>ls</code>, <code>grep</code>, <code>cp</code> работают как по живому дереву;</li>
<li>место растёт примерно на объём изменений, а не на полный размер каждый день;</li>
<li>удалили файл на проде — в новом слепке его нет (<code>--delete</code>), в старом он ещё лежит, пока слепок не выкинете.</li>
</ul>
<p>Hardlink — это не копия и не симлинк. Два имени, один inode. Пока на inode есть хотя бы одна ссылка, данные живы. Выкинули самый старый слепок — <code>unlink</code> уменьшил счётчик, и только для тех файлов, которые больше ни в одном слепке не встречаются, место реально освободилось.</p>
<p>Важное следствие, из-за которого люди потом материются: правка файла <em>внутри</em> слепка правит все слепки, которые ссылаются на тот же inode. Бэкап — не рабочая копия. Залезли поправить «на всякий случай конфиг в бэкапе» — поправили историю. Restore всегда копированием в другой каталог, никогда правкой на месте.</p>
<h5>Раскладка каталогов, с которой потом не стыдно жить</h5>
<p>Я не нумерую <code>snapshot.0</code> … <code>snapshot.7</code>, если нет rsnapshot. Номера крутятся <code>mv</code>, в панике в три ночи легко перепутать, что сейчас «сегодня». Даты читаются без расшифровки:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">/backup/host-a/
    2026-08-25/
    2026-08-26/
    2026-08-27/
    latest -&gt; 2026-08-27</pre>
<p>Отдельный корневой каталог на хост. Не сваливать сайты разных машин в одну кучу: hardlink работает только внутри одной файловой системы, а путать, чей это <code>wp-config.php</code>, в аварии дорого.</p>
<p>Диск под бэкапы — отдельный раздел или отдельный диск. Не <code>/var</code> той же машины, которую снимаете. И не NFS, если рассчитываете на hardlink: на NFS hardlink либо запрещён, либо ведёт себя так, что экономии не получите, а <code>du</code> сойдёт с ума. <code>--link-dest</code> требует, чтобы каталог назначения и каталог link-dest были на одной ФС. Проверка перед первым запуском:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">df -P /backup/host-a | awk 'NR==1 || {print $1, $6}'
stat -f -c '%i' /backup/host-a   # filesystem id, должен совпасть у всех слепков</pre>
<p>Если снимаете несколько источников на один раздел — нормально. Если бэкап-диск собрали из двух mount — rsync молча начнёт копировать файлы вместо hardlink, место кончится через несколько ночей, в логе это будет не ошибкой.</p>
<h5>Первый слепок и все следующие</h5>
<p>Первый прогон — обычный полный rsync, без <code>--link-dest</code>. Каталога для ссылок ещё нет.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">SRC=/var/www/site
ROOT=/backup/host-a
TODAY=$(date +%F)

mkdir -p "$ROOT/$TODAY"
rsync -aH --numeric-ids --delete --one-file-system \
  "$SRC/" "$ROOT/$TODAY/"
ln -sfn "$TODAY" "$ROOT/latest"</pre>
<p>Следующие ночи — тот же приём, плюс ссылка на предыдущий слепок. Предыдущий я беру не из <code>latest</code> до обновления симлинка, а явно: последний каталог с датой, который не равен сегодняшнему. Если ночной прогон перезапустили дважды за сутки, второй раз должен писать в тот же <code>$TODAY</code>, а не плодить <code>2026-08-27-1</code>.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">SRC=/var/www/site
ROOT=/backup/host-a
TODAY=$(date +%F)
DEST="$ROOT/$TODAY"
PREV=$(find "$ROOT" -mindepth 1 -maxdepth 1 -type d -name '20*' \
        ! -path "$DEST" | sort | tail -n1)

mkdir -p "$DEST"
RSYNC_OPTS=(-aH --numeric-ids --delete --one-file-system --partial)

if [ -n "$PREV" ]; then
  rsync "${RSYNC_OPTS[@]}" --link-dest="$PREV" "$SRC/" "$DEST/"
else
  rsync "${RSYNC_OPTS[@]}" "$SRC/" "$DEST/"
fi

ln -sfn "$TODAY" "$ROOT/latest"</pre>
<p>Слэш после <code>$SRC/</code> обязателен: копируем содержимое, а не каталог «site» внутрь слепка. Без слэша через год получите <code>/backup/host-a/2026-08-27/site/...</code> вперемешку со старыми прогонами, где слэш уже стоял.</p>
<p>По флагам, без которых потом больно:</p>
<ul>
<li><code>-a</code> — права, времена, симлинки, устройства. Без этого слепок «как файлы», не «как система».</li>
<li><code>-H</code> — сохранить hardlink <em>в источнике</em>. Это не про <code>--link-dest</code>. Если на проде два имени на один inode, без <code>-H</code> в бэкапе станут две копии.</li>
<li><code>--numeric-ids</code> — uid/gid цифрами, без подстановки имён. На бэкап-сервере пользователь <code>www-data</code> часто другой uid. Без флага права «починятся» молча и неправильно.</li>
<li><code>--delete</code> — в новом слепке нет того, чего уже нет на источнике. Старые слепки это не трогает.</li>
<li><code>--one-file-system</code> — не уехать в <code>/mnt</code>, bind-mount и чужой NFS, который примонтировали «на пять минут».</li>
</ul>
<p>ACL и xattr на нормальном сайте часто не нужны. Если нужны — добавляю <code>-AX</code>, и проверяю, что приёмник их умеет. На копии «через промежуточный tar на FAT» они умрут, hardlink тут ни при чём.</p>
<p>Тянуть по SSH с другой машины — тот же набор, источник <code>user@host-a:/var/www/site/</code>. Ключ только для чтения нужных путей, не root с интерактивным шеллом «потому что так проще». Хостнеймы в скриптах и known_hosts — свои, не клиентские FQDN из тикета.</p>
<h5>Что rsync считает «тем же файлом»</h5>
<p>По умолчанию — размер и mtime. Совпали оба, содержимое не читает. Для дерева из сотен тысяч php/jpg это правильно: иначе каждую ночь будете гонять диск впустую.</p>
<p>Где это врёт:</p>
<ul>
<li>бэкап-скрипт или деплой делают <code>touch</code> по дереву — mtime новый, rsync решит, что всё изменилось, hardlink не состоится, слепок станет почти полным;</li>
<li>наоборот: файл переписали, mtime оставили (редкость, но бывает у кривых копировщиков) — в слепке останется старое содержимое через hardlink;</li>
<li>права/владелец поменялись, размер и mtime нет — с обычным <code>-a</code> rsync всё же обновит метаданные. С hardlink это тонкое место: смена владельца на inode видна во всех слепках, которые на него ссылаются. На практике для «сайт + загруженные картинки» почти не всплывает. На домашних каталогах с постоянным <code>chown</code> — всплывает.</li>
</ul>
<p><code>--checksum</code> лечит второй случай и наказывает диск. Я включаю его точечно, не «на всякий случай каждый день». Для проверки целого слепка после аварии — да. Для ночного прогона магазина с десятками гигабайт upload — нет.</p>
<p>Ещё одна мина: <code>--inplace</code>. Он пишет в существующий файл. Существующий файл в новом слепке после hardlink — это тот же inode, что вчера. <code>--inplace</code> вместе с <code>--link-dest</code> портит историю. Не включать.</p>
<h5>Что не класть в слепок живьём</h5>
<p>Rsync снимает файлы. Он не делает консистентный снимок СУБД и не понимает очереди почты.</p>
<p>MySQL/MariaDB: ночью <code>mysqldump</code> (или <code>mariadb-dump</code>) в файл, и в слепок идёт дамп, не <code>/var/lib/mysql</code>. Снимать datadir на работающем сервере — лотерея с битым InnoDB. Если очень нужен сырой datadir — только после <code>FLUSH TABLES WITH READ LOCK</code> / горячего снимка LVM/ZFS, и это уже другая схема, не «просто rsync».</p>
<p>PostgreSQL: <code>pg_dump</code> или <code>pg_basebackup</code>, не <code>rsync</code> по <code>base/</code> на живой базе.</p>
<p>Сессии PHP, кэш, <code>node_modules</code>, очереди — либо exclude, либо смириться, что слепок на 80% состоит из мусора, который ещё и каждый раз «изменился». Типичный exclude для сайта:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">/wp-content/cache/
/wp-content/uploads/cache/
/tmp/
/var/tmp/
*.log</pre>
<p>Передаю через <code>--exclude-from=/etc/backup/site.excludes</code>. Не размазывать exclude по трём скриптам: через полгода никто не вспомнит, почему картинки из <code>uploads</code> вдруг не попали в слепок.</p>
<p>Полный бэкап системы — отдельный разговор. <code>/proc</code>, <code>/sys</code>, <code>/dev</code>, <code>/run</code> не снимают. <code>--one-file-system</code> как раз спасает от этого, если корневой слепок берёте с <code>/</code>. Загрузчик, UUID в fstab, криптозаголовки — rsync их «как файлы» скопирует, но загрузиться с такого каталога просто так нельзя. Для «откатить сайт» этого достаточно. Для «восстановить весь сервер» я не выдаю rsync-слепок за образ диска.</p>
<h5>Место на диске: почему du врёт, если мерить по одному каталогу</h5>
<p>Заходите в слепок — <code>du -sh 2026-08-27</code> показывает почти полный размер сайта. Так и должно быть: внутри каталога почти все файлы «настоящие» с точки зрения дерева, просто inode общие с соседом. Смотреть надо сумму по корню и сравнение «по отдельности vs вместе»:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">du -sh /backup/host-a
du -sh /backup/host-a/20*
du -sh --apparent-size /backup/host-a/20*</pre>
<p>Первая строка — сколько занято уникальными данными. Список по датам — каждый слепок выглядит толстым. <code>--apparent-size</code> ближе к «логическому» объёму дерева. Если уникальный <code>du</code> по корню прыгает на размер всего сайта после очередной ночи — hardlink не сработал. Типичные причины: слепки на разных ФС, первый прогон без <code>--link-dest</code> в уже существующий каталог с другими inode, массовый <code>touch</code>, копирование через промежуточный диск без сохранения inode.</p>
<p>Иноды кончаются раньше байтов, если мелких файлов много. WordPress с кэшем и тучей php это умеет:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">df -h /backup
df -i /backup</pre>
<p>Если <code>IUse%</code> под 80%, а место ещё есть — либо чистить старые слепки, либо не снимать то, что exclude должен был отсечь.</p>
<p>Ротация у меня тупая и предсказуемая: хранить 7 дневных, плюс «первое число месяца», если место позволяет. Удалять целиком каталог даты, не файлы изнутри:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash"># всё старше 14 дней, кроме 01-го числа
find /backup/host-a -mindepth 1 -maxdepth 1 -type d -name '20*' \
  | sort | head -n -14 | while read -r d; do
      b=$(basename "$d")
      case $b in *-01) continue ;; esac
      rm -rf "$d"
    done</pre>
<p><code>rm -rf</code> по hardlink-дереву безопасен: уменьшает nlink, чужие слепки не трогает. Не делать <code>chmod -R a-w</code> по слепку «чтобы никто не правил». chmod пишет в inode. Общий inode — права поменяются и во вчерашнем слепке. «Защита от записи» — монтированием раздела <code>ro</code> на чтение, отдельным пользователем без записи, не chmod по живым hardlink.</p>
<h5>Проверка restore: код 0 у rsync ничего не доказывает</h5>
<p>Ночной прогон вернул 0. Это значит: rsync отработал. Это не значит, что из слепка поднимется сайт. Я отдельно гоняю проверку, хотя бы раз в неделю, на копию в песочницу, не на прод.</p>
<p>Минимум, который реально ловит беду:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">SNAP=/backup/host-a/2026-08-27
TEST=/tmp/restore-test.$$
mkdir -p "$TEST"

# копируем, не линкуем: иначе снова общий inode
rsync -aH --numeric-ids "$SNAP/" "$TEST/"

# дерево не пустое, ключевые файлы на месте
test -s "$TEST/wp-config.php"
test -d "$TEST/wp-content/uploads"

# сравнение числа файлов — расхождение сразу видно
echo -n "snap:  "; find "$SNAP" -type f | wc -l
echo -n "copy:  "; find "$TEST" -type f | wc -l

# несколько случайных файлов — содержимое не нулевое и совпадает
find "$SNAP" -type f | shuf -n 20 | while read -r f; do
  rel=${f#"$SNAP"/}
  cmp -s "$SNAP/$rel" "$TEST/$rel" || echo "DIFF $rel"
done</pre>
<p>Если это дамп базы — мало «файл не пустой». Нужен пробный импорт в отдельный экземпляр и <code>SELECT COUNT(*)</code> по жирной таблице, плюс открыть пару записей глазами. Дамп в 200 байт с ошибкой доступа тоже «файл на месте».</p>
<p>Если бэкап тянется по SSH — раз в неделю снимаю md5/sha256 списка ключевых файлов на источнике и в свежем слепке. Не всего дерева: достаточно конфигов, <code>.env</code>, пары известных загрузок. Расхождение mtime при том же содержимом меня не пугает. Расхождение содержимого — уже инцидент, даже если сайт «вроде открывается».</p>
<p>Что я ещё смотрю после ночного прогона, не дожидаясь недели:</p>
<ul>
<li>код выхода rsync и последние строки лога: <code>rsync error</code>, обрыв по SSH, <code>vanished files</code> пачкой;</li>
<li>размер корня <code>du -sh /backup/host-a</code> относительно вчера — скачок на полный объём сайта;</li>
<li>что симлинк <code>latest</code> указывает на сегодняшнюю дату, а не застрял на прошлой неделе;</li>
<li>что в слепке нет пустого дерева из-за опечатки в источнике (rsync прекрасно синхронизирует пустой каталог и с <code>--delete</code> вычистит новый слепок подчистую, старые не трогая).</li>
</ul>
<p>Пустой источник — отдельный класс аварии. Защита простая: если <code>find "$SRC" -type f | wc -l</code> меньше порога, скрипт выходит, не вызывая rsync. Порог свой для каждого дерева, не «больше нуля».</p>
<h5>Типичные поломки, которые я уже не ищу по второму разу</h5>
<p><strong>Слэш и не тот каталог.</strong> Источник без завершающего <code>/</code>, destination уже с предыдущим мусором. Слепок «есть», сайта внутри нет, зато есть вложенная лишняя директория.</p>
<p><strong>Два слепка на разных ФС.</strong> <code>--link-dest</code> молча деградирует в полное копирование. Лечится только тем, что <code>df</code> смотрят до, а не после письма «диск кончился».</p>
<p><strong>Запуск от пользователя, который не читает часть дерева.</strong> Rsync пропускает файлы с Permission denied и в конце может вернуть код 23. Часть людей глотает 23 как «ну почти ноль». Для бэкапа 23 — это дырка. Либо root на чтение, либо доступ по группе, либо явный список, и код 23 будит, а не пишется в лог «на всякий случай».</p>
<p><strong>Живая база в слепке.</strong> Сайт после restore «открывается», заказы за вчерашние полдня разъехались. Dump рядом со слепком файлов, одной датой.</p>
<p><strong>chmod/chown по слепкам.</strong> История переписана во всех датах, которые делили inode.</p>
<p><strong>Один и тот же destination каждый раз, а &#8211;link-dest в соседнюю копию «для экономии».</strong> Если destination не чистый каталог новой даты, а вечно живой <code>/backup/current</code>, вы снова приехали к схеме «есть только сегодня». <code>--link-dest</code> имеет смысл, когда новый слепок — новый каталог.</p>
<p><strong>Cron в локальной полночи и пик записи на сайте.</strong> Для файлового дерева это обычно терпимо: максимум файл середины заливки. Для заказов и почты — нет. Файлы ночью, дамп базы в окно минимальной нагрузки, не наоборот.</p>
<h5>Когда этого достаточно, а когда уже нет</h5>
<p><code>rsync --link-dest</code> хорошо закрывает: сайт, домашние каталоги, конфиги, каталог загрузок, «хочу зайти и забрать вчерашний wp-content». Прозрачность тут важнее степени сжатия. Я могу отдать человеку путь и сказать: копируй отсюда. Без fuse, без <code>restic mount</code>, без сюрприза «репозиторий не открывается, ключ от другого хоста».</p>
<p>Плохо закрывает: много маленьких постоянно меняющихся файлов (тотальный выигрыш hardlink падает), нужна дедупликация между разными машинами, нужен шифрованный репозиторий на чужой площадке. Туда Borg/restic. Не вместо проверки restore — поверх другой модели хранения.</p>
<p>Обёртки вроде rsnapshot делают ровно это же, плюс ротацию <code>hourly.0</code>. Если ставит человек, который не хочет писать скрипт — пусть ставит. Если уже наступил на chmod и на NFS, лучше свой скрипт на два экрана: в нём видно, куда смотреть, когда слепок внезапно стал полным по диску.</p>
<p>И последнее, без чего схему не считаю рабочей: хотя бы один успешный restore в отдельный каталог, с поднятым экземпляром или хотя бы с проверенным дампом. Пока этого не было, у вас не бэкап, а ночной rsync с красивыми датами на каталогах.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/linux/inkrementalnyj-bekap-cherez-rsync-link-dest-polnye-slepki-hardlink-i-proverka-restore/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Ремонт проводки задних дверей Opel Zafira A (левая и правая)</title>
		<link>https://remadmin.com/blog/auto/remont-provodki-zadnih-dverej-opel-zafira-a-levaya-i-pravaya/</link>
					<comments>https://remadmin.com/blog/auto/remont-provodki-zadnih-dverej-opel-zafira-a-levaya-i-pravaya/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Tue, 25 Aug 2026 17:24:34 +0000</pubDate>
				<category><![CDATA[Авто]]></category>
		<category><![CDATA[Opel]]></category>
		<category><![CDATA[Zafira]]></category>
		<category><![CDATA[гофра]]></category>
		<category><![CDATA[проводка]]></category>
		<category><![CDATA[стеклоподъёмник]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7385</guid>

					<description><![CDATA[Обрыв жгута в гофре задних боковых дверей Zafira A. Левая и правая — разные жгуты и распиновка. Как снять разъём, таблица контактов и ремонт с запасом длины.]]></description>
										<content:encoded><![CDATA[<p>Речь про <strong>боковые задние двери</strong> Opel Zafira A — левую и правую. Не про пятую дверь багажника.</p>
<p>Типичная болячка платформы Zafira A / Astra G: в резиновой гофре между <strong>стойкой кузова</strong> и торцом двери переламывается жгут. Симптомы обычно по одной двери:</p>
<ul>
<li>стеклоподъёмник то работает, то нет;</li>
<li>не отрабатывает ЦЗ этой двери;</li>
<li>пропал или хрипит звук из этой двери (провода динамика идут тем же жгутом);</li>
<li>на приборке нет индикации открытой двери.</li>
</ul>
<p>Менять замок, мотор стеклоподъёмника и сам динамик чаще всего рано. Дверь изнутри разбирать не нужно: жгут от двери <strong>отстёгивается разъёмом на торце</strong>, а ремонтируется та часть проводки, которая уходит в кузов через гофру (стойка / порог).</p>
<p>Левая и правая — разные жгуты и разная распиновка. Чините правую — не копируйте цвета с левой.</p>
<h5>Почему ломается</h5>
<p>На передних дверях жгут между петлями живёт спокойнее. На задних — короткий жгут в узкой гофре, изоляция штатных жил жёсткая, длины почти в обрез. При закрытии двери гофра складывается вместе с проводами — усталостный обрыв в зоне перегиба, часто рядом с колодкой со стороны двери или там, где жгут входит в стойку.</p>
<p>Смятая, потрескавшаяся или уже разрезанная гофра — почти верный признак, что внутри уже плохо или скоро будет.</p>
<h5>Что нужно</h5>
<ul>
<li>мультиметр;</li>
<li>пайка или медные гильзы + термоусадка;</li>
<li>мягкий провод (силикон / фторопласт): сигнальные ~0.5–0.75 мм², силовые стеклоподъёмника заметно толще;</li>
<li>по желанию — более мягкая гофра и смазка, чтобы при закрытии двери провода уходили в стойку, а не ломались в перегибе.</li>
</ul>
<p>Минус АКБ снять сразу: толстый красный на мотор стекла часто под постоянным плюсом.</p>
<h5>Как добраться до жгута (дверь не разбираем)</h5>
<p>Всё делается снаружи торца двери и из салона у стойки.</p>
<ol>
<li>Открыть заднюю дверь. На переднем нижнем торце — многоконтактная колодка, вокруг неё стопорное кольцо (часто синее).</li>
<li>Нажать/сжать кольцо и вытянуть разъём <strong>из двери</strong>. Дверь остаётся в сборе: обшивку, динамик, замок не трогаем.</li>
<li>Снять пластиковое кольцо/фиксатор, которым гофра держится на колодке.</li>
<li>Со стороны кузова освободить гофру из отверстия в стойке. При необходимости снять накладку порога / низ облицовки стойки — так жгут удобнее вытянуть в салон.</li>
<li>Вытянуть в салон именно кузовную часть жгута вместе с гофрой и осмотреть жилы. Удобный приём: после снятия разъёма слегка прикрыть заднюю дверь и работать из проёма передней — жгут подаётся вперёд, к обрывам проще подобраться.</li>
</ol>
<p>Оборванный провод часто находится натяжением: целые держат, оборванный выезжает отдельно. Типичное место — зона постоянного перегиба в гофре.</p>
<p>До резки: фото колодки и маркировка каждого провода. Левый и правый жгуты потом легко перепутать.</p>
<h5>Чем левая дверь отличается от правой</h5>
<ul>
<li>на разборке жгуты идут отдельно «задняя левая» / «задняя правая»;</li>
<li>свои цепи: мотор стекла, активатор ЦЗ, концевик, динамик — у сторон разные;</li>
<li>расцветка тонких жил (особенно акустика и управление стеклом с водительского пульта) слева и справа не обязана совпадать — «по аналогии с другой дверью» как раз и ошибаются;</li>
<li>общее у обеих: толстый красный (+) и толстый коричневый (масса) мотора стеклоподъёмника — силовая пара, её ломает чаще всего.</li>
</ul>
<h5>Типовая распиновка разъёма (ориентир)</h5>
<p>Это колодка на стыке «дверь <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> жгут в кузов». Ниже — рабочий ориентир по ролям контактов (LHD, типовая комплектация). Пустые номера — резерв/опции. Точные цвета тонких жил сверяйте на своей стороне двери.</p>
<table>
<tbody>
<tr>
<th>Контакт (ориентир)</th>
<th>Типичный цвет</th>
<th>Сечение</th>
<th>Назначение</th>
</tr>
<tr>
<td>1</td>
<td>красный</td>
<td>~2.5 мм²</td>
<td>+12В мотора стеклоподъёмника (часто постоянный плюс)</td>
</tr>
<tr>
<td>2</td>
<td>коричневый</td>
<td>~2.5 мм²</td>
<td>масса мотора стеклоподъёмника</td>
</tr>
<tr>
<td>3</td>
<td>серо‑белый</td>
<td>~0.75</td>
<td>центральный замок</td>
</tr>
<tr>
<td>4</td>
<td>белый</td>
<td>~0.75</td>
<td>центральный замок</td>
</tr>
<tr>
<td>5</td>
<td>чёрно‑зелёный</td>
<td>~0.75</td>
<td>доп. запирание / anti‑theft (если есть)</td>
</tr>
<tr>
<td>6</td>
<td>серый</td>
<td>~0.5</td>
<td>концевик открытой двери</td>
</tr>
<tr>
<td>9</td>
<td>жёлто‑коричневый</td>
<td>~0.5</td>
<td>служебные/охранные линии (по комплектации)</td>
</tr>
<tr>
<td>10</td>
<td>бело‑синий</td>
<td>~0.5</td>
<td>блокировка задних стеклоподъёмников</td>
</tr>
<tr>
<td>11</td>
<td>фиолетово‑зелёный</td>
<td>~0.5</td>
<td>управление электростеклом</td>
</tr>
<tr>
<td>12</td>
<td>фиолетово‑коричневый</td>
<td>~0.5</td>
<td>управление электростеклом</td>
</tr>
<tr>
<td>16</td>
<td>зелёный (часто)</td>
<td>~0.75</td>
<td>динамик (тем же жгутом в кузов)</td>
</tr>
<tr>
<td>17</td>
<td>коричнево‑зелёный (часто)</td>
<td>~0.75</td>
<td>динамик</td>
</tr>
<tr>
<td>19</td>
<td>коричнево‑чёрный</td>
<td>~0.5</td>
<td>комфортное закрытие стёкол (если есть)</td>
</tr>
</tbody>
</table>
<p>Перед наращиванием: прозвонка от потребителя до обрыва и сверка цветов на стойке и на ответной части <strong>этой же</strong> двери. Силовую пару с сигнальными не путать. Даже если «умерло» только стекло — осмотрите соседние жилы.</p>
<h5>Как ремонтировать</h5>
<ol>
<li>Вырезать повреждённый участок на кузовной стороне жгута (в зоне гофры / входа в стойку).</li>
<li>Вставить мягкий провод с запасом длины, чтобы жгут мог уходить в стойку при закрытии двери, а не стоять внатяг.</li>
<li>Стыки (пайка + термоусадка или обжим) вынести <strong>из зоны постоянного перегиба</strong> — внутрь стойки/порога, не оставлять «шишку» посередине гофры.</li>
<li>Собрать гофру, воткнуть разъём обратно в торец двери, проверить на этой двери: стекло, ЦЗ, звук, концевик.</li>
</ol>
<p>Чтобы гофра снова не убила жгут: чуть смазки на провода и/или более мягкая гофра — при закрытии двери жилы должны ускользать в кузов, а гофра сжиматься сама.</p>
<h5>Готовые ремкомплекты с маркетплейсов</h5>
<p>Можно не нарезать провода вручную: на маркетплейсах продают готовые ремкомплекты жгута задней двери (мягкая проводка + разъём/пины под штатную колодку). У Опелей они с виду похожи, но <strong>не взаимозаменяемые вслепую</strong>.</p>
<ul>
<li>отличаются по поколению (Zafira A ≠ Zafira B, ловите дорест/рест);</li>
<li>отдельно под левую и под правую заднюю дверь;</li>
<li>сверьте фото разъёма и комплектацию в карточке со своей колодкой до заказа.</li>
</ul>
<p>Берите ремкомплект <strong>именно под свою машину</strong> и <strong>именно под ту дверь</strong>, которую чините.</p>
<p>Итого: дверь не разбираем. Отщёлкнули разъём на торце, вытянули кузовную часть жгута из стойки, нарастили/поменяли участок в зоне гофры, собрали обратно. Левая и правая — разные жгуты; цвета с «другой стороны» не копировать.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/auto/remont-provodki-zadnih-dverej-opel-zafira-a-levaya-i-pravaya/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Steal time и странный load на VPS: как отличить нехватку CPU у гипервизора от реальной нагрузки</title>
		<link>https://remadmin.com/blog/linux/steal-time-i-strannyj-load-na-vps-kak-otlichit-nehvatku-cpu-u-gipervizora-ot-realnoj-nagruzki/</link>
					<comments>https://remadmin.com/blog/linux/steal-time-i-strannyj-load-na-vps-kak-otlichit-nehvatku-cpu-u-gipervizora-ot-realnoj-nagruzki/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Tue, 25 Aug 2026 16:57:09 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[iowait]]></category>
		<category><![CDATA[steal time]]></category>
		<category><![CDATA[VPS]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7382</guid>

					<description><![CDATA[Load на VPS врёт: очередь растёт и от диска, и от steal time гипервизора, и от квоты cgroup. Как за пять минут отличить нехватку CPU у ноды от реальной нагрузки приложения — по top, mpstat, iostat и cpu.stat.]]></description>
										<content:encoded><![CDATA[<p>Типичная заявка: «сайт тормозит, load под десятку, добавьте ядер». Захожу на VPS — два vCPU, load 8–9, в <code>top</code> почти всё idle. PHP-FPM в состоянии D, MySQL тоже. Хостер клянётся, что «ресурсы выданы полностью». Дальше обычно начинается шаманство: крутят <code>pm.max_children</code>, рестартуют php-fpm, добавляют swap. Это почти никогда не то место, где лежит причина.</p>
<p>На виртуалке load врёт чаще, чем на железе. Он не отделяет «мои процессы жрут CPU» от «диск лёг» и от «гипервизор забрал кванты времени у соседа». Если не разложить эти три штуки, будете лечить не того.</p>
<h5>Что на самом деле считает load average</h5>
<p>Load — это не «загрузка процессора в процентах». Это средняя длина очереди: сколько задач либо готовы бежать на CPU, либо спят в uninterruptible sleep (состояние D, обычно ожидание диска или NFS).</p>
<p>Поэтому картина «load 12 при 70% idle» на двух ядрах — нормальная, если в D висит десяток php-fpm, а диск отвечает по 80–200 мс. Процессор свободен, очередь растёт. С steal time та же история: задача хочет CPU, гипервизор её не планирует, очередь тоже растёт, а в <code>top</code> вы видите idle плюс <code>st</code>.</p>
<p>Смотрю сразу три числа: load за 1/5/15 минут и сколько ядер (<code>nproc</code>). Load 4 на четырёх ядрах — ещё можно жить. Load 4 на одном vCPU уже несколько минут — уже инцидент. Пятнадцатиминутный load запаздывает: если тормоза начались три минуты назад, ориентируйтесь на единицу и на живые счётчики, не на «среднее за четверть часа».</p>
<h5>Три столбца в top, без которых диагностика — гадание</h5>
<p>В шапке <code>top</code> (клавиша <code>1</code> включает разбивку по ядрам) мне важна строка CPU:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">%Cpu(s):  8.4 us,  3.1 sy,  0.0 ni, 41.0 id, 27.2 wa,  0.0 hi,  1.1 si, 19.2 st</pre>
<p>Коротко по делу:</p>
<ul>
<li><code>us</code> + <code>sy</code> — ваши процессы реально крутят код. Вот это «нехватка CPU у приложения».</li>
<li><code>id</code> — простой. Высокий idle сам по себе ничего не лечит и ничего не доказывает.</li>
<li><code>wa</code> — iowait: процессор свободен, но есть незавершённый I/O. Это не «диск загрузил CPU», это ожидание.</li>
<li><code>st</code> — steal: гипервизор отдал эти тики не вам. Ваша виртуалка хотела считать — ей не дали.</li>
<li><code>si</code> — softirq. Если внезапно большой на фоне сети, смотрю пакеты и netfilter, а не PHP.</li>
</ul>
<p>Правило, которым я пользуюсь в первые 30 секунд: если <code>us+sy</code> низкие, а load высокий — это не «надо больше ядер приложению». Это либо диск, либо гипервизор, либо cgroup-троттлинг, который в <code>st</code> может вообще не попасть.</p>
<h5>Steal time: когда виноват не ваш код, а сосед по ноде</h5>
<p>Steal — доля времени, когда vCPU был готов работать, но гипервизор забрал слот. На нормально выделенном KVM с гарантией CPU <code>st</code> обычно около нуля, редкие всплески на 1–2% можно не драматизировать. На дешёвых VPS вечером я регулярно вижу 10–30% steal пачками по несколько минут. Формально у вас «2 ядра», фактически в этот момент — полтора или меньше.</p>
<p>Как это ощущается снаружи: сайт отвечает рывками, CPU в панели хостера «зелёный», процессы в <code>top</code> не жрут по 100%, латентность прыгает без изменения трафика. Внутри в это же время растёт очередь PHP/очередь диска — не потому что MySQL внезапно стал писать в десять раз больше, а потому что кванты CPU приходят дыряво.</p>
<p>Подтверждаю не одним кадром <code>top</code>, а рядом:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">nproc
uptime
mpstat -P ALL 1 30
vmstat 1 30</pre>
<p>В <code>mpstat</code> смотрю столбец <code>%steal</code> по каждому vCPU. Если steal сидит на всех ядрах стабильно выше примерно 5–8% и при этом <code>%usr/%sys</code> не упёрлись в потолок — это не ваше приложение «уперлось в CPU». Это нода. Если steal скачет нуль–сорок нуль–сорок синхронно на всех vCPU — классика оверселла, «шумный сосед» или ночной бэкап гипервизора.</p>
<p>В <code>vmstat</code> те же буквы в последнем столбце <code>st</code>, плюс <code>r</code> (run queue) и <code>b</code> (blocked on IO). Высокий <code>r</code> при низком <code>us</code> и ненулевом <code>st</code> — задачи ждут планировщика, а не вашего кода.</p>
<p>Важный нюанс: на части облаков CPU режут не через steal, а через кредиты/квоты. Тогда <code>st</code> может быть нулевым, а приложение всё равно ползёт. Это уже следующий раздел, не путайте с «честным» KVM-steal.</p>
<h5>Iowait: load большой, процессор скучает, диск не отвечает</h5>
<p>Вторая частая ловушка. Load 10, idle 60%, <code>wa</code> 30–40%. В <code>ps</code> куча процессов в D. Человек видит load и думает «CPU». Нет: очередь — на I/O.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">ps -eo pid,user,state,wchan:24,etime,cmd | awk 'NR==1 || $3 ~ /D/'
iostat -xz 1 20
vmstat 1 20</pre>
<p>В <code>iostat</code> мне важны не «красивые» MB/s, а <code>await</code>, <code>aqu-sz</code> и <code>%util</code>. Если на vda/vdb await уехал в десятки–сотни миллисекунд при скромном объёме записи — это не «MySQL плохо настроен», это сторадж ноды. На дешёвом VPS диск почти всегда общий. Сосед запустил бэкап — ваш InnoDB начинает ждать fsync.</p>
<p>Отдельно проверяю swap:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">free -h
vmstat 1 10</pre>
<p>Ненулевые <code>si/so</code> в <code>vmstat</code> плюс высокий iowait — это уже не «мало CPU», это память кончилась и система молотит страницы. Добавление ядер тут бесполезно. И увеличение <code>pm.max_children</code> в такой ситуации обычно делает хуже: больше процессов — больше давления на диск и RAM.</p>
<p>NFS, CIFS, зависший iSCSI дают ту же картину: D-state, растущий load, CPU почти idle. <code>wchan</code> в <code>ps</code> часто прямо намекает (<code>io_schedule</code>, <code>nfs</code>, <code>wait_on_page</code>). Не лечите это рестартом php-fpm, пока не поняли, на чём они спят.</p>
<h5>Реальная нагрузка: когда процессы правда жрут CPU</h5>
<p>Это самый скучный и самый честный случай. <code>us+sy</code> под 80–100% на всех ядрах, steal около нуля, iowait низкий, в <code>top</code> по CPU видны конкретные pid: php-fpm, mysqld, node, python. Load примерно согласован с числом ядер и run queue.</p>
<p>Тогда уже имеет смысл:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">pidstat -u 1 10
ps -eo pid,user,pcpu,pmem,cmd --sort=-pcpu | head
# если есть perf и можно ненадолго:
# perf top -g</pre>
<p>Искать медленный запрос, воркера без лимита, cron, который внезапно переложил всю таблицу. Здесь апгрейд CPU поможет. На steal и на дохлый диск — нет, вы просто будете дороже ждать то же самое.</p>
<p>Не путайте короткий всплеск (деплой, прогрев кэша, один тяжёлый отчёт) с системной нехваткой. Смотрите 5-минутное окно, не один кадр.</p>
<h5>Четвёртая ловушка: cgroup throttle без steal</h5>
<p>На контейнерах (LXC, часть «VPS из панели», Kubernetes) CPU часто режут квотой cgroup, а не классическим steal. В <code>top</code> тогда <code>st=0</code>, idle может быть приличным, а латентность всё равно плохая: вас просто не пускают на CPU сверх квоты.</p>
<p>На cgroup v2:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">cat /sys/fs/cgroup/cpu.stat
cat /sys/fs/cgroup/cpu.max
cat /proc/pressure/cpu</pre>
<p>В <code>cpu.stat</code> смотрю <code>nr_throttled</code> и <code>throttled_usec</code>. Если throttled растёт на глазах — вас режут. <code>cpu.max</code> вида <code>20000 100000</code> на двух видимых vCPU означает, что «два ядра» в <code>nproc</code> — картинка, а лимит другой.</p>
<p>PSI (<code>/proc/pressure/cpu</code> и <code>io</code>) удобен тем, что показывает stall some/full за 10/60/300 секунд. <code>full</code> по CPU — никто не мог работать. Это ближе к правде, чем load, когда хочется понять «прямо сейчас нас душили или пять минут назад».</p>
<p>На старых OpenVZ/Virtuozzo картина ещё мутнее: иногда steal есть, иногда только failcnt в beancounters. Если попали на такое — не делайте выводы по одному <code>top</code>, смотрите лимиты панели и <code>cpu.stat</code>.</p>
<h5>Порядок, которым я отличаю гипервизор от реальной нагрузки</h5>
<p>Не распухающий чеклист, а последовательность на пять минут.</p>
<ol>
<li>Снять размер машины и load: <code>nproc</code>, <code>uptime</code>, <code>free -h</code>. Понять, не swap ли это вообще.</li>
<li>Живой <code>top</code>, клавиша <code>1</code>. Записать us/sy/id/wa/st. Не скриншот «процессы», а шапку CPU.</li>
<li><code>mpstat -P ALL 1 30</code> и <code>vmstat 1 30</code> — steal стабильный или вспышками, run queue, blocked.</li>
<li>Если wa заметный — <code>iostat -xz 1 20</code> и список D-процессов с <code>wchan</code>.</li>
<li>Если st≈0 и CPU не упирается — <code>cpu.stat</code> / <code>cpu.max</code> / <code>/proc/pressure/cpu</code>.</li>
<li>Только если us+sy высокие и лимиты не душат — иду в pidstat и уже в приложение.</li>
</ol>
<p>За эти пять минут обычно ясно, в какую из четырёх корзин класть инцидент. Дальше уже либо тикет хостеру с цифрами, либо диск, либо квота, либо профилирование PHP/SQL.</p>
<h5>Что не делать, пока не поставили диагноз</h5>
<p>Не поднимать <code>pm.max_children</code> «чтобы очередь рассосалась». На iowait и на steal это увеличивает очередь, а не уменьшает.</p>
<p>Не добавлять swap «для запаса», если si/so уже ненулевые. Вы лечите нехватку RAM ещё большим iowait.</p>
<p>Не рестартить MySQL по кругу, если await диска сотни миллисекунд. Рестарт в этот момент — лотерея с порчей и долгим recovery.</p>
<p>Не просить «ещё два ядра» у хостера, имея на руках только load average. Load на оверсолде растёт и от steal, и от диска. Без <code>mpstat/iostat</code> вам продадут тот же оверсолл подороже — или, что чаще, просто перекинут на такую же ноду.</p>
<h5>Как разговаривать с хостером, чтобы это не было «сайт тормозит»</h5>
<p>Тикет без цифр на оверсолде заканчивается шаблоном «на ноде всё хорошо, смотрите приложение». Я прикладываю 30 секунд <code>mpstat -P ALL</code>, кусок <code>vmstat</code>, если надо — <code>iostat</code>, время (UTC), и формулировку в духе: «на 2 vCPU стабильно %steal 15–25 при %usr+%sys &lt; 30, load растёт, iowait такой-то. Прошу проверить contention на ноде / переезд». Не имена клиентов, не внутренние IP, не «у нас прод упал».</p>
<p>Если хостер нормальный, на устойчивом steal они переезжают. Если нет — имеет смысл менять площадку, а не тюнить opcache. Оптимизация приложения на 10% не лечит потерю четверти CPU каждую минуту.</p>
<p>Для себя оставляю те же логи локально на время разбора. Через час steal может исчезнуть, и вы останетесь с «ну у нас сейчас всё зелёное».</p>
<h5>Коротко: как не перепутать корзины</h5>
<p><strong>Реальная нагрузка:</strong> us+sy высокие, st≈0, wa низкий, в top видны конкретные процессы. Лечите код, запросы, лишние воркеры. Ядра помогут.</p>
<p><strong>Нехватка CPU у гипервизора:</strong> st стабильно заметный, us+sy не упираются, load и латентность прыгают без роста трафика. Лечите площадку, не php.ini.</p>
<p><strong>Диск:</strong> wa высокий, процессы в D, await большой. Часто на тех же дешёвых VPS соседствует со steal — нода оверsold и по CPU, и по стораджу. Сначала цифры, потом выводы.</p>
<p><strong>Квота cgroup:</strong> st=0, cpu.stat throttled растёт, PSI full по CPU. «Два ядра» в панели ≠ два ядра в планировщике.</p>
<p>Пока эти четыре строки не разложены, любой совет «увеличьте лимиты PHP» — гадание. Снимается это стандартными утилитами за несколько минут, без агентов и без панелей. Панель хостера в этот момент почти всегда врёт в оптимистичную сторону: она рисует «выданные» ядра, а не те, которые вам реально отдали.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/linux/steal-time-i-strannyj-load-na-vps-kak-otlichit-nehvatku-cpu-u-gipervizora-ot-realnoj-nagruzki/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>