<?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/tags/shpargalka/feed/" rel="self" type="application/rss+xml" />
	<link>https://remadmin.com</link>
	<description>Удалённый системный администратор</description>
	<lastBuildDate>Fri, 09 Oct 2026 07:45:41 +0000</lastBuildDate>
	<language>ru-RU</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>
	<item>
		<title>SSH ProxyJump вместо проброса ключа на сервер: когда безопаснее agent forwarding</title>
		<link>https://remadmin.com/blog/linux/ssh-proxyjump-vmesto-probrosa-kljucha-na-server-kogda-bezopasnee-agent-forwarding/</link>
					<comments>https://remadmin.com/blog/linux/ssh-proxyjump-vmesto-probrosa-kljucha-na-server-kogda-bezopasnee-agent-forwarding/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Fri, 09 Oct 2026 07:45:41 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[bastion]]></category>
		<category><![CDATA[ProxyJump]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[ssh-agent]]></category>
		<category><![CDATA[безопасность]]></category>
		<category><![CDATA[шпаргалка]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7436</guid>

					<description><![CDATA[Копировать ключ на bastion или включить ForwardAgent «чтобы удобно» — разные дыры. Как я хожу через прыжок без ключа на джампе и когда агент всё-таки нужен.]]></description>
										<content:encoded><![CDATA[<p>Типичная заявка: «на прод с ноутбука не пускает, только через bastion, я скопировал свой <code>id_ed25519</code> на прыжок, теперь захожу в два ssh». Вариант «культурнее»: «включили <code>ForwardAgent</code>, чтобы не вводить пароль от ключа дважды, и Ansible сам прыгает». Через месяц на джампе кто-то с root снимает сокет агента и ходит на прод вашим ключом. Ключ при этом «не крали» — его и не надо красть, агент сам подписывает.</p>
<p>Я не таскаю закрытый ключ на промежуточную машину. Прыжок для меня — дырка в TCP, не второй дом для <code>ssh-agent</code>. Ниже — чем <code>ProxyJump</code> отличается от проброса агента, когда агент всё-таки оставляю, и чем проверяю, что ключ с ноутбука никуда не уехал.</p>
<p>Хосты в примерах вымышленные: <code>jump.example.net</code> (документационный <code>203.0.113.40</code>), внутренний <code>prod-web-01.internal</code>, пользователь <code>ops</code>. Свои имена и ключи подставляйте, чужие из чатов не копируйте.</p>
<h5>Две привычки, которые путают с «удобным прыжком»</h5>
<p>Первая: положить тот же закрытый ключ на bastion. «Ну я же сам туда захожу, чего его беречь». Bastion — это машина, на которую смотрит интернет. На ней чаще стоят общие учётки, старый sudo, чужой tmux. Если ключ от прода лежит файлом в <code>~/.ssh</code> на такой коробке, компрометация прыжка равна компрометации всего, куда этот ключ пускают. Пароль на ключ тут слабое утешение: его снимут вместе с файлом, из памяти агента или из вашей же сессии.</p>
<p>Вторая: <code>ForwardAgent yes</code> глобально в <code>~/.ssh/config</code> или в системном <code>ssh_config</code>. Так делают, чтобы со прыжка открыть следующий ssh «как с ноутбука». Удобно. И ровно поэтому опасно. На время сессии на джампе появляется unix-сокет агента. Любой, кто может читать этот сокет — root, ваша скомпрометированная учётка, чужой процесс под тем же uid — может пользоваться ключами, пока вы онлайн. Ключ с диска не копировали. Подпись от вашего агента уже достаточно.</p>
<p>Есть ещё гибрид: ключ на ноутбуке, а на прыжок его один раз залили «на всякий случай» и забыли. Потом ротировали ключ дома, на джампе старый остался. Я такие находки встречаю чаще, чем хотелось бы: <code>ls -l ~/.ssh</code> на bastion и два <code>id_*</code>, один из которых уже «не наш».</p>
<h5>Что именно делает агент, когда его пробросили</h5>
<p><code>ssh-agent</code> на ноутбуке — нормальная вещь. Он держит расшифрованные ключи в памяти, чтобы не тыкать passphrase на каждый <code>git fetch</code>. Плохо не агент. Плохо отдавать его сокет чужой машине.</p>
<p>При <code>ForwardAgent yes</code> клиент поднимает на удалённой стороне канал и выставляет <code>SSH_AUTH_SOCK</code>. Дальше <code>ssh prod-web-01</code>, запущенный уже <em>на прыжке</em>, ходит в этот сокет. Подпись делает ваш ноутбук. Выглядит безопасно: «ключ же дома». Но решение «подписать вот этот challenge» принимает процесс на bastion. Если bastion врет, вы подпишете не то соединение, о котором думаете.</p>
<p>Практическая картинка, которую я показываю заказчику:</p>
<ul>
<li>Вы в сессии на <code>jump.example.net</code>.</li>
<li><code>echo $SSH_AUTH_SOCK</code> даёт путь вроде <code>/tmp/ssh-XXXX/agent.1234</code>.</li>
<li>С правами на этот сокет можно сделать <code>ssh-add -L</code> и увидеть ваши ключи.</li>
<li>Дальше — обычный <code>ssh ops@prod-web-01.internal</code>. Прод видит ваш ключ. В authorized_keys это тот же самый ключ, которым вы заходите «сами».</li>
</ul>
<p>Root на джампе для этого не всегда нужен. Достаточно вашей же утёкшей сессии, crон-скрипта под тем же uid или «временного» бинарника в <code>$PATH</code>. Поэтому фраза «ну у нас же на bastion только свои» меня не греет: свои тоже ошибаются, а агент не спрашивает, кто именно попросил подпись.</p>
<p>Отдельно: <code>ssh-add -c</code> на ноутбуке чуть лучше, чем голый агент. Перед каждой подписью всплывает confirm. Это не броня, но если сокет увели тихо, вы хотя бы увидите лишний запрос в момент, когда сами ничего не открывали.</p>
<h5>ProxyJump: прыжок без вашего ключа на той стороне</h5>
<p><code>ProxyJump</code> (ключ <code>-J</code>) делает другое. Клиент на ноутбуке открывает ssh на bastion и просит его прокинуть TCP до цели. Аутентификация на <code>prod-web-01</code> идёт с ноутбука, напрямую в этом тоннеле. На прыжке нет вашего закрытого ключа, нет сокета агента, нет второго <code>ssh</code> от вашего имени.</p>
<p>Грубо: bastion видит, что с вашего IP пришёл ssh и что вы попросили соединиться с <code>prod-web-01:22</code>. Bastion не видит, каким ключом вы представились проду, и не может этот ключ переиспользовать через минуту после того, как вы отключились.</p>
<p>Минимальный <code>~/.ssh/config</code>, с которого я начинаю:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">Host jump
    HostName jump.example.net
    User ops
    IdentityFile ~/.ssh/id_ed25519_jump
    IdentitiesOnly yes
    ForwardAgent no

Host prod-web-01
    HostName prod-web-01.internal
    User ops
    ProxyJump jump
    IdentityFile ~/.ssh/id_ed25519_prod
    IdentitiesOnly yes
    ForwardAgent no</pre>
<p>Два ключа — не паранойя. Ключ на интернет-джамп я считаю расходным: его проще отозвать. Ключ на прод на прыжок не кладу и в агент для сессии на jump не пихаю, если можно не пихать. <code>IdentitiesOnly yes</code> обязателен: иначе клиент переберёт все ключи из агента, и вы сами не вспомните, чем открыли хост. Плюс лишние попытки ключей иногда заканчиваются <code>Too many authentication failures</code>.</p>
<p>Одноразово без конфига:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ssh -J ops@jump.example.net ops@prod-web-01.internal</pre>
<p>Два и больше прыжка — список через запятую:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ssh -J jump.example.net,dmz-gw.example.net ops@prod-web-01.internal</pre>
<p>В конфиге то же самое: <code>ProxyJump jump,dmz-gw</code>. Старый <code>ProxyCommand ssh -W %h:%p jump</code> я оставляю только там, где клиент древний и <code>ProxyJump</code> ещё нет. Смысл тот же: проброс TCP, не проброс агента.</p>
<h5>Как я проверяю, что агент не уехал</h5>
<p>После захода на цель смотрю три вещи, не «ну вроде работает».</p>
<p>На ноутбуке, до входа:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ssh -G prod-web-01 | egrep -i 'proxyjump|proxycommand|forwardagent|identityfile|identitiesonly'</pre>
<p>Хочу увидеть свой <code>ProxyJump</code>, <code>forwardagent no</code>, нужные <code>IdentityFile</code> и <code>identitiesonly yes</code>. Если <code>forwardagent yes</code> приехало из <code>/etc/ssh/ssh_config</code> или из <code>Host *</code> в конце файла — чиню там, а не добавляю ещё один <code>Host</code> выше, который потом перебьют.</p>
<p>Вход с отладкой:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ssh -v prod-web-01 2&gt;&amp;1 | egrep -i 'proxy|jump|forwarding|agent|Offering|Authentication succeeded'</pre>
<p>Ищу, что клиент идёт через jump и что нет строк про agent forwarding. «Offering public key» должно относиться к ключу прода, не к ключу джампа. Если клиент предлагает пять ключей подряд — <code>IdentitiesOnly</code> не сработал.</p>
<p>На самой цели и на прыжке, уже внутри сессии:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">echo "SSH_AUTH_SOCK=${SSH_AUTH_SOCK:-}"
ssh-add -l</pre>
<p>На <code>prod-web-01</code> при чистом ProxyJump сокет агента обычно пустой, <code>ssh-add -l</code> отвечает, что агента нет. На прыжке при обычном заходе на сам jump — то же самое, если я не просил проброс. Если сокет вдруг есть, а я его не включал, ищу кто: <code>Host *</code>, Include-info в конфиге, обёртка вроде <code>assh</code>/<code>sshpass</code>, корпоративный «улучшатель» ssh.</p>
<p>Ещё одна проверка, которую люди пропускают: ключи на диске прыжка.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ls -l ~/.ssh
find ~/.ssh -type f \( -name 'id_*' -o -name '*.pem' \) ! -name '*.pub'</pre>
<p>Закрытых ключей там быть не должно. Если есть — снимаю с машины, смотрю <code>authorized_keys</code> на проде, не торчит ли тот же pubkey, и ротирую. Pubkey на прыжке (чтобы вы сами на него заходили) — нормально. Приватник от прода — нет.</p>
<h5>Когда агент всё-таки нужен — и как его не размазать</h5>
<p>Иногда без проброса не обойтись. Типичные случаи, которые я оставляю точечно:</p>
<ul>
<li>С удалённой машины нужно сходить в git по SSH вашим ключом, а ключ на эту машину класть нельзя. Тогда агент — меньшее зло, чем файл в <code>~/.ssh</code>.</li>
<li>Старый сценарий, который сам делает <code>ssh</code> со прыжка на пачку хостов и не умеет <code>ProxyJump</code>. Это чинят, но не в пять минут посреди инцидента.</li>
<li>Промежуточный хост должен подписать что-то ещё вашим ключом (редко, и я сначала пытаюсь перенести это действие на ноутбук).</li>
</ul>
<p>Правило одно: агент включаю на конкретный <code>Host</code>, не на <code>*</code>. И только на время задачи.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">Host git-build.internal
    HostName git-build.internal
    User ops
    ProxyJump jump
    IdentityFile ~/.ssh/id_ed25519_prod
    IdentitiesOnly yes
    ForwardAgent yes</pre>
<p>На ноутбуке ключ в агент кладу с подтверждением:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ssh-add -c ~/.ssh/id_ed25519_prod</pre>
<p>Сессию короче держу. После работы <code>ssh-add -d</code> этот ключ из агента. На прыжке в <code>sshd_config</code> я по возможности ставлю <code>AllowAgentForwarding no</code> — тогда даже ошибочный <code>ForwardAgent yes</code> у клиента не поднимет сокет. Если агент нужен на одну машину за прыжком, форвардинг агента разрешаю только там, не на bastion.</p>
<p>Ansible отдельным абзацем. Плейбук, который заходит на прод через bastion, не обязан пробрасывать агент. В инвентаре достаточно:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ansible_ssh_common_args: '-o ProxyJump=ops@jump.example.net'</pre>
<p>или <code>ansible_ssh_extra_args</code> с тем же. Ключ остаётся на контрольной машине. Если вам «без <code>ForwardAgent</code> плейбук не едет», чаще виноват не Ansible, а то, что роли сами делают <code>ssh</code>/<code>rsync</code>/<code>git</code> уже на целевом хосте вашим ключом. Это другая задача: либо ключ деплоя на целевой машине, либо отдельный deploy key в git, либо вынести шаг на ноутбук.</p>
<h5>Что должно быть на самом прыжке</h5>
<p>Bastion я собираю скучно. Чем меньше на нём живёт, тем меньше соблазна «раз уж зашли, давайте тут ключи».</p>
<ul>
<li>Отдельный ключ только для входа на jump. Этот ключ не пускает на прод.</li>
<li><code>PermitRootLogin no</code>, вход по паролю выключен, по возможности отдельный порт или хотя бы geo/fail2ban — как принято у заказчика, не догма.</li>
<li><code>AllowTcpForwarding yes</code> (иначе ProxyJump не взлетит), <code>AllowAgentForwarding no</code>, <code>X11Forwarding no</code>.</li>
<li>Если sshd умеет <code>PermitOpen</code> — ограничиваю, куда вообще можно прыгать: только рабочие подсети, не «любой порт мира через нас».</li>
<li>Нет общих <code>ops</code> с одним ключом на пятерых. Если ключ общий, вы не отзовёте одного человека.</li>
<li>На диске нет <code>id_*</code> без <code>.pub</code>. Нет <code>authorized_keys</code> с ключами прода «для удобства».</li>
</ul>
<p>Кусок <code>sshd_config</code> на прыжке, от которого я отталкиваюсь:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">AllowAgentForwarding no
AllowTcpForwarding yes
X11Forwarding no
PermitTunnel no
GatewayPorts no
# если политика позволяет — режем направления:
# PermitOpen prod-web-01.internal:22 prod-db-01.internal:22</pre>
<p><code>PermitOpen</code> легко забыть и потом час ловить <code>administratively prohibited</code>. Сначала проверяю, что ProxyJump жив без него, потом сужаю. Логи sshd на прыжке смотрю не «красиво ли», а кто и куда просил forward: если из сессии <code>ops</code> вдруг полезли на адрес, которого в заявке не было, это уже не удобство, а инцидент.</p>
<h5>Мелочи, из-за которых «прыжок не работает»</h5>
<p>Частая жалоба: «с <code>-J</code> меня не пускает, а два ручных ssh работают». Почти всегда одно из трёх.</p>
<p>Первое — разные пользователи. На jump вы <code>ops</code>, на проде <code>deploy</code>, а в команде указали одного. В конфиге это два <code>User</code>. В <code>-J</code> — явно <code>ops@jump</code> и <code>deploy@prod</code>.</p>
<p>Второе — DNS. Ноутбук не резолвит <code>prod-web-01.internal</code>, а bastion резолвит. <code>ProxyJump</code> соединяет туда, куда сказал <em>клиент</em>. Если клиент не знает имя, пишу в <code>HostName</code> адрес, который виден с прыжка, либо завожу имя в локальный <code>/etc/hosts</code> только как метку. Обратная ловушка: в <code>HostName</code> поставили внешний IP прода, которого с прыжка нет — ручной второй ssh с jump шёл на внутреннее имя, а ProxyJump пытается в белый адрес.</p>
<p>Третье — ключи и агент. Клиент предлагает ключ джампа на прод, прод отвечает «не тот» несколько раз и отваливается. Лечится <code>IdentitiesOnly</code> и отдельным <code>IdentityFile</code>. Если ключ в агенте один, а файлов много, агент всё равно может предложить не то: поэтому для прод-хостов я указываю файл явно.</p>
<p>Четвёртое, реже: <code>ControlMaster</code>. Мультиплекс на jump удобен, но старый сокет иногда держит чужие опции. Если после правки конфига поведение «как раньше», сношу сокеты <code>~/.ssh/cm-*</code> и проверяю ещё раз.</p>
<h5>Что я отказываюсь считать решением</h5>
<p>Не считаю решением скопировать закрытый ключ на bastion «только на время работ». Файл переживает работы. Его найдёт следующий, кто зайдёт под той же учёткой.</p>
<p>Не считаю решением глобальный <code>ForwardAgent yes</code> «потому что так в wiki». Wiki часто писали, когда <code>ProxyJump</code> ещё не было в клиенте по умолчанию. Сейчас клиент это умеет давно.</p>
<p>Не считаю решением один ключ на всё: ноутбук, jump, прод, git, CI. Отзыв после утечки тогда равен «перевыпустить жизнь». Два-три ключа с понятной ролью дешевле нервов.</p>
<p>Не считаю решением «у нас на джампе свои люди, нам не надо». Свои люди оставляют tmux, ставят тестировать бинарник, дают sudo на час. Агент в этот час работает на всех.</p>
<p>Считаю решением короткую схему, которую можно проговорить вслух: ключ прода живёт на ноутбуке, прыжок только прокидывает TCP, агент выключен, кроме одной явно названной машины, и на диске bastion нет ни одного <code>id_*</code> без суффикса <code>.pub</code>. Это скучно. Зато когда bastion однажды вскроют — а интернет-машину вскрывают — прод не откроется тем же ключом из чужой сессии.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/linux/ssh-proxyjump-vmesto-probrosa-kljucha-na-server-kogda-bezopasnee-agent-forwarding/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ИБП и удалёнка: порядок выключения NAS/ПК, чтобы не словить порчу ФС</title>
		<link>https://remadmin.com/blog/umnyj-dom/ibp-i-udaljonka-poryadok-vykljucheniya-nas-pk-chtoby-ne-slovit-porchu-fs/</link>
					<comments>https://remadmin.com/blog/umnyj-dom/ibp-i-udaljonka-poryadok-vykljucheniya-nas-pk-chtoby-ne-slovit-porchu-fs/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Tue, 22 Sep 2026 20:31:17 +0000</pubDate>
				<category><![CDATA[Умный дом]]></category>
		<category><![CDATA[NAS]]></category>
		<category><![CDATA[NUT]]></category>
		<category><![CDATA[ИБП]]></category>
		<category><![CDATA[сеть]]></category>
		<category><![CDATA[файловая система]]></category>
		<category><![CDATA[шпаргалка]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7431</guid>

					<description><![CDATA[ИБП без сценария выключения — просто отложенный hard power-off. Как я гашу клиенты раньше NAS, кто должен держать USB, и что проверить утром после обрыва 220.]]></description>
										<content:encoded><![CDATA[<p>Типичная заявка звучит так: «свет моргнул, я был в отъезде, утром NAS не монтирует том, Windows просит chkdsk, а в логах SMB куча обрывов». Вариант похуже: том поднялся, фото на месте, зато база 1С или iSCSI-лун после ночи «как будто обрезали посередине записи». Ещё чаще люди ставят ИБП «на всякий случай», вешают на него NAS, ПК и чайник, а сценарий выключения не прописывают вообще. Батарея держит минуты, потом ИБП сам щёлкает нагрузку — и это уже не «мягкое выключение», а обычный hard power-off, только с задержкой.</p>
<p>Я не продаю «умный дом, который сам всё спасёт». Мне нужно одно: когда я не дома, машины должны успеть сами размонтировать диски и погаснуть в правильном порядке. Ниже — как я это собираю, что ломается, если порядок перепутать, и что смотреть утром, если ночью свет всё-таки ушёл.</p>
<p>Хосты в примерах вымышленные: <code>nas.home.lan</code>, <code>pc-office</code>, <code>nut-master</code>. Свои имена, VLAN и модель ИБП подставляйте.</p>
<h5>Почему «ИБП стоит» ≠ «файловая система целая»</h5>
<p>Журнал ext4, NTFS dirty bit, ZFS intent log — это подушки. Они уменьшают шанс полной смерти тома, но не отменяют простой факт: если в момент отключения шёл сброс кэша, дописывался RAID-страйп или клиент держал открытый файл по SMB, вы ловите как минимум долгий fsck и «тихую» порчу одного файла. На NAS это особенно обидно: локальный диск ПК ещё можно пережить, а общий том — уже заявка «откати бэкап, но не вчерашний».</p>
<p>ИБП даёт время. Не бесконечное. Бытовой line-interactive на NAS + роутер + коммутатор часто держит от нескольких минут до четверти часа под реальной нагрузкой. Если за это время никто не начал shutdown, батарея просто кончится. Тогда ИБП либо уйдёт в bypass и потухнет, либо сам отрубит розетки. Для дисков разницы почти нет: питание пропало на работающей ФС.</p>
<p>Отдельная ловушка — «умные» розетки и сценарии вроде «через 60 секунд после пропадания 220 отключить NAS, чтобы экономить батарею». Это не shutdown. Это тот же hard power-off, только реле сработало красиво. Резать питание можно только после того, как ОС сама погасла.</p>
<h5>Порядок, который я держу в голове</h5>
<p>Клиенты с сетевыми дисками выключаются раньше хранилища. Хранилище — раньше, чем сеть, по которой ему пришла команда. Монитор ИБП (тот, у кого USB в ИБП) гаснет последним из компьютеров. Коммутатор и роутер живут до последнего: без них команда shutdown до NAS не доедет.</p>
<p>На пальцах для типичной квартиры/небольшого офиса:</p>
<ul>
<li>Короткий моргающий свет (секунды) — ничего не выключаю. Только пишу в лог <code>ONBATT</code> и, если настроено, шлю уведомление.</li>
<li>Питание так и не вернулось, батарея ушла в «мало» или осталось несколько минут рантайма — начинаю сценарий.</li>
<li>Сначала ПК, тонкие клиенты, виртуалки, которые держат SMB/NFS/iSCSI на NAS. Им нужно сбросить кэш и отпустить файлы.</li>
<li>Потом сам NAS: unmount, flush, штатный poweroff. DSM/TrueNAS на это могут занять пару минут, не секунды.</li>
<li>Потом хост, который видит ИБП по USB (NUT master). Он же в конце может дать ИБП команду снять нагрузку.</li>
<li>Роутер и коммутатор не трогаю сценарием. Они умрут сами, когда ИБП снимет розетки — к этому моменту компьютеры уже должны быть off.</li>
</ul>
<p>Перепутать два места легко. Первое: NAS выключился, а <code>pc-office</code> ещё дописывает проект на <code>\\nas\share</code>. Клиент получит обрыв, локальный кэш SMB может соврать, а на томе останется недописанный файл. Второе: выключили коммутатор «чтобы экономить батарею», и NAS, который должен был принять команду по сети, её уже не услышит. Дальше NAS живёт до нуля батареи и падает как есть.</p>
<h5>Кто вообще имеет право нажимать shutdown</h5>
<p>USB от ИБП физически торчит в один хост. Не в два сразу: hid-драйвер не шарится. Этот хост становится источником правды: он читает <code>OL</code>/<code>OB</code>/<code>LB</code>, считает рантайм и рассылает остальным «пора гаснуть».</p>
<p>Три рабочие схемы, которые я встречаю:</p>
<ul>
<li>USB в NAS (Synology/TrueNAS). NAS сам гаснет по своему «UPS». Остальные машины либо клиенты NUT/Synology UPS Server, либо их вообще нет в сценарии. Минус: если ПК с примонтированными шарами не клиент, он переживёт NAS и умрёт позже на пустой батарее.</li>
<li>USB в отдельный Linux («<code>nut-master</code>»). Классика Network UPS Tools: master видит ИБП, slaves слушаются. Порядок я тогда рисую скриптом на master, а не надеюсь, что все slaves погаснут «примерно одновременно».</li>
<li>Вендорский агент на Windows (PowerChute и аналоги). Работает, пока в контуре один ПК. Как только появляется NAS с шарами, Windows-агент про NAS не думает. Либо добавляю NUT/SSH со стороны Linux, либо сознательно оставляю NAS единственным, кто умеет гаснуть.</li>
</ul>
<p>Правило, которое я записываю заказчику в одну строку: кто держит диски с чужими сессиями — тот не имеет права умереть первым. NAS с открытыми SMB — не первый. Хост с USB — не первый, если он должен ещё раздать команду.</p>
<h5>NUT: master, slave и почему «все slave» — плохой порядок</h5>
<p>На <code>nut-master</code> минимум такой каркас. Имена и пароли вымышленные, в прод так не копировать.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># /etc/nut/ups.conf
[officeups]
    driver = usbhid-ups
    port = auto
    desc = "office line-interactive"

# /etc/nut/upsd.conf
LISTEN 127.0.0.1 3493
LISTEN 192.168.10.10 3493

# /etc/nut/upsd.users
[upsmon]
    password = "replace-me"
    upsmon master

# /etc/nut/upsmon.conf
MONITOR officeups@localhost 1 upsmon replace-me master
MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h now"
POWERDOWNFLAG /etc/killpower
FINALDELAY 5</pre>
<p><code>LISTEN</code> только во внутреннюю сеть, не на белый IP. <code>upsd</code> — не веб-морда.</p>
<p>На NAS или втором Linux slave выглядит так:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">MONITOR officeups@192.168.10.10 1 upsmon replace-me slave
SHUTDOWNCMD "/sbin/shutdown -h now"</pre>
<p>Если и <code>pc-office</code>, и <code>nas.home.lan</code> оба slave, NUT при FSD скажет им гаснуть почти вместе. Для меня этого мало. Клиенту нужно 20–40 секунд на unmount, NAS — минуты на сброс томов. Поэтому на master я не полагаюсь на одновременный FSD, а вешаю свой <code>NOTIFYCMD</code>: сначала гашу клиентов по SSH, жду, потом NAS, и только потом локальный shutdown.</p>
<p>Грубый скелет, без прикрас:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">#!/bin/bash
# /usr/local/sbin/ups-shutdown-order.sh
# вызывается из NOTIFYCMD на ONBATT+LB или FSD, не на каждый ONBATT
set -eu
clients="pc-office.home.lan htpc.home.lan"
nas="nas.home.lan"

for h in $clients; do
  ssh -o ConnectTimeout=5 "shutdown@$h" 'sudo /sbin/shutdown -h +0' || true
done
sleep 45

ssh -o ConnectTimeout=8 "shutdown@$nas" 'sudo /sbin/shutdown -h now' || true
# DSM: часто удобнее synoshutdown / shutdown из их sudoers, не systemd
sleep 90

/sbin/shutdown -h now</pre>
<p>Таймауты не универсальные. На живом NAS я один раз засекаю, сколько он гаснет от команды до того, как погасли дисковые LED. Это и есть нижняя граница <code>sleep</code> плюс запас. Если DSM выключается 2,5 минуты, <code>sleep 30</code> — способ убить том почти наверняка: master погас, ИБП снял нагрузку, NAS ещё писал.</p>
<p>Доступ <code>shutdown@</code> — отдельная учётка с sudo только на <code>shutdown -h</code>, ключ без пароля, только с <code>nut-master</code>. Не админ «на все команды».</p>
<h5>Когда начинать: ONBATT — ещё не повод рубить всё</h5>
<p>Короткий провал 220 я не превращаю в массовый reboot. Иначе каждый сварочный сосед и каждый ввод резерва на ТП будут по ночам гасить офис.</p>
<p>Смотрю статус так:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">upsc officeups@localhost
# важные поля:
# ups.status     OL / OB / LB / FSD / CHRG
# battery.charge
# battery.runtime</pre>
<p><code>OL</code> — сеть есть. <code>OB</code> — работаем от батареи. <code>LB</code> — мало. <code>FSD</code> — forced shutdown уже идёт.</p>
<p>Практическая схема, которой я пользуюсь:</p>
<ul>
<li><code>ONBATT</code> без <code>LB</code> — лог, письмо или Telegram, ничего не гашу. Жду 30–120 секунд: свет часто возвращается.</li>
<li>Порог по рантайму, не по красивому проценту. «20%» на старой батарее может быть одной минутой. Смотрю <code>battery.runtime</code> и засечённое время реального гашения контура.</li>
<li>Старт сценария, когда остатка хватает на: ожидание клиентов + shutdown NAS + запас. Если весь контур гасится 3 минуты, порог «осталось 4 минуты» — уже игра в кости.</li>
<li>Калибровку батареи и «сколько реально держит» делаю раз в сезон, локально, не в первую командировку.</li>
</ul>
<p>Вендорские галочки в DSM вроде «выключить при 15%» без замера рантайма я не доверяю. Процент врёт чаще, чем секунды.</p>
<h5>Что именно вешать на батарейные розетки</h5>
<p>На «battery backup», не на «surge only»:</p>
<ul>
<li>роутер и коммутатор, через который NAS получает команду;</li>
<li>сам NAS;</li>
<li>хост с USB в ИБП;</li>
<li>ПК, которые обязаны штатно погаснуть, а не «ну переживём chkdsk».</li>
</ul>
<p>Не вешаю лазерный принтер, обогреватель, большой монитор «ну раз розетка свободна». Они съедают минуты, которые нужны дискам. PoE-коммутатор с кучей камер может сожрать батарею быстрее NAS — тогда камеры либо на отдельный маленький ИБП, либо сознательно не входят в контур «спасти ФС».</p>
<p>Отдельный камень: модем провайдера. Если он не на ИБП, уведомление «свет пропал» из дома вы не получите: канал умрёт вместе с 220. На shutdown это не влияет — сценарий локальный. На ваше спокойствие в поездке — влияет. Я либо питаю ONT/модем от того же ИБП, либо не жду, что телега доедет.</p>
<h5>Windows, DSM, TrueNAS — где люди обычно срезают угол</h5>
<p>Windows по умолчанию не слушает NUT. Варианты, которые работают у меня без вендорской простыни: WinNUT-Client как slave, либо SSH/WinRM с master и штатный <code>shutdown /s /t 0 /f</code>. <code>/f</code> закрывает приложения принудительно — для ночного обрыва питания это меньшее зло, чем диалог «сохранить документ» до конца батареи. Для интерактивного ПК днём — неприятно, поэтому порог LB, а не первый ONBATT.</p>
<p>Synology: «Панель управления → Оборудование и питание → ИБП». Если USB в NAS, включаю сетевой UPS-сервер Synology, чтобы другой их же NAS или NUT-совместимый клиент получил сигнал. Если USB в Linux — DSM настраиваю как NUT slave на <code>nut-master</code>. Проверяю, что команда реально гасит DSM, а не пишет «событие ИБП» в журнал и живёт дальше. Разница видна один раз, если посидеть рядом с LED дисков.</p>
<p>TrueNAS: служба UPS — тот же NUT. Не забываю <code>SHUTDOWNCMD</code> и что jail/VM должны уехать до zpool. Иначе пул экспортируется, пока виртуалка ещё пишет в zvol.</p>
<p>systemd на Linux: <code>shutdown -h</code> лучше, чем <code>systemctl halt</code> без понимания, уйдёт ли питание. Для NUT master ещё важен <code>POWERDOWNFLAG /etc/killpower</code> и проверка <code>upsmon -K</code> на старте: ИБП после пачки shutdown должен снять нагрузку, а когда 220 вернётся — подать её снова, чтобы машины включились. Без этого батарея догорит в ноль на уже выключенных ПК, и утром часть железа не встанет, пока кто-то не нажмёт кнопку на ИБП.</p>
<h5>Как машины должны просыпаться, когда свет вернулся</h5>
<p>Выключение — половина. В BIOS/UEFI хоста и NAS смотрю «Restore AC Power Loss» / «After Power Loss»:</p>
<ul>
<li>NAS — обычно Always On. Хранилище должно подняться само.</li>
<li><code>nut-master</code> — тоже Always On. Иначе следующий обрыв никто не отследит.</li>
<li>Офисный ПК — Last State или Off. Мне редко нужно, чтобы он сам ревел в пустой комнате.</li>
</ul>
<p>Wake-on-LAN тут ни при чём: его некому послать, пока вы спите в другом городе. Это про поведение блока питания после возврата 220.</p>
<p>Роутер на ИБП сам поднимется. Если у провайдера ONT долго регистрируется, это не повод задерживать shutdown: к моменту возврата света компьютеры уже должны быть холодные.</p>
<h5>Как я это проверяю, не надеясь на «первый настоящий обрыв»</h5>
<p>Первый тест — только локально, днём, с руками на кнопке ИБП и с копией важных томов, которая не на этом же NAS.</p>
<ul>
<li><code>upsc</code> в норме: <code>OL</code>, рантайм похож на правду, USB не отваливается в dmesg.</li>
<li>Выдёргиваю входную вилку ИБП из стены, не нагрузку. Статус должен стать <code>OB</code>. Клиенты ещё живы.</li>
<li>Жду порог или форсирую сценарий. Смотрю, что <code>pc-office</code> погас первым, шары отпустились, потом погас NAS, потом master.</li>
<li>Засекаю время от старта сценария до момента, когда на NAS нет активности дисков. Это число кладу в <code>sleep</code> и в порог батареи.</li>
<li>Втыкаю вилку обратно. ИБП берёт сеть, поднимает нагрузку, NAS стартует сам.</li>
</ul>
<p>Форсировать FSD командой NUT можно, но на чужом контуре в пятницу вечером я этого не делаю. Слишком легко получить тот самый обрыв, от которого писали статью.</p>
<p>После теста:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># Linux / TrueNAS
journalctl -u nut-monitor -u nut-server --since today
zpool status
dmesg -T | tail

# DSM: Storage Manager / журналы ИБП, не «всё зелёное в виджете»

# Windows
wevtutil qe System /q:"*[System[EventID=6008 or EventID=6006 or EventID=41]]" /c:5 /f:text</pre>
<p><code>6008</code> — неожиданное выключение. Если он есть после «успешного» сценария, Windows погасили питанием, не <code>shutdown /s</code>. Сценарий врёт.</p>
<h5>Утро после настоящего обрыва, когда меня не было дома</h5>
<p>Короткий список, без магии:</p>
<ul>
<li>Поднялся ли NAS сам. Если нет — сначала ИБП и «Restore AC», не fsck.</li>
<li>Тома смонтировались без ручного repair. На ZFS — <code>zpool status</code>, нет ли FAULTED/UNAVAIL после внезапного питания. На DSM — Storage Manager, не «папка открывается в File Station».</li>
<li>Клиенты, которые писали ночью (бэкап, 1С, камеры на шару). Открыть не «файл на месте», а хвост: последняя запись, целостность архива, не обрезан ли контейнер.</li>
<li>Журнал NUT: был ли <code>OB</code> → сценарий → shutdown, или сразу смерть на <code>OL</code> (тогда ИБП не участвовал: выбили автомат после ИБП, сдох USB, уехал драйвер).</li>
<li>Рантайм в логе. Если от <code>OB</code> до смерти прошло меньше, чем длится shutdown NAS — порог надо поднимать или разгружать ИБП.</li>
<li>Батарея после серии обрывов. Старый аккумулятор «держит на экране 12 минут» и отдаёт три — это уже не настройка NUT, а замена батареи. Цены тут не считаю: если ИБП пищит и рантайм сдулся, это расходник, не «ещё чуть крутануть процент».</li>
</ul>
<p>Если том всплыл, а один каталог странный — не чиню «на горячую» и не запускаю агрессивный repair, пока нет копии. Сначала снимок/бэкап, потом fsck. ИБП спасает от внезапного питания, но не от желания починить живой диск первой попавшейся галочкой.</p>
<h5>Что я отказываюсь считать решением</h5>
<p>Не считаю решением плагин умного дома «выключить розетку NAS через минуту». Не считаю решением «поставим ИБП побольше и не будем ничего гасить» — батарея всё равно кончится в длинный обрыв, плюс вы греете комнату зря. Не считаю решением только уведомление в мессенджер: в 03:00 я могу не успеть, а канал может умереть вместе с модемом.</p>
<p>Считаю решением короткую цепочку, которую можно проговорить вслух: кто видит USB, кто гаснет первым, сколько минут это занимает, сколько батарея реально держит, и что должно включиться само, когда 220 вернётся. Это скучно. Зато утром NAS монтирует том, а не просит «ваш диск был извлечён неправильно».</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/umnyj-dom/ibp-i-udaljonka-poryadok-vykljucheniya-nas-pk-chtoby-ne-slovit-porchu-fs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Application Passwords vs XML-RPC: что оставлять и как не открыть лишнюю дыру</title>
		<link>https://remadmin.com/blog/vebmasteru/application-passwords-vs-xml-rpc-chto-ostavlyat-i-kak-ne-otkryt-lishnjuju-dyru/</link>
					<comments>https://remadmin.com/blog/vebmasteru/application-passwords-vs-xml-rpc-chto-ostavlyat-i-kak-ne-otkryt-lishnjuju-dyru/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Tue, 22 Sep 2026 20:30:05 +0000</pubDate>
				<category><![CDATA[Вебмастеру]]></category>
		<category><![CDATA[Application Passwords]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[Wordpress]]></category>
		<category><![CDATA[xmlrpc]]></category>
		<category><![CDATA[безопасность]]></category>
		<category><![CDATA[логи]]></category>
		<category><![CDATA[Уязвимость]]></category>
		<category><![CDATA[шпаргалка]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7429</guid>

					<description><![CDATA[XML-RPC и Application Passwords — разные входы. Как я закрываю xmlrpc.php на nginx, почему фильтр xmlrpc_enabled не считается закрытием, и как не оставить токен приложения с правами администратора.]]></description>
										<content:encoded><![CDATA[<p>Типичная заявка: «поставил плагин безопасности, галочка Disable XML-RPC стоит, 2FA на админке включена, а в логах всё равно долбят xmlrpc.php». Вариант помягче: «выдал подрядчику логин администратора, чтобы скрипт заливал новости, потом сменил пароль — скрипт отвалился, а через неделю на сайте чужие черновики». Ещё чаще путают двери: закрыли xmlrpc, зато в профиле пользователя висит Application Password с именем «test», который никто не отзывал полгода.</p>
<p>Это не одна настройка. XML-RPC — старый вход в PHP-файл. Application Passwords — отдельные пароли к REST API (и, если xmlrpc не закрыт, ещё и к нему же). Двухфакторка на <code>wp-login.php</code> вторую дверь не закрывает. Ниже — как я решаю, что оставлять, что резать на nginx, и где «плагин запретил xmlrpc» оставляет дыру, которой хватает боту.</p>
<p>Хосты в примерах вымышленные: <code>shop.example</code>. Свои пути, пользователя и префикс логов подставляйте.</p>
<h5>Две двери, не «один API WordPress»</h5>
<p>На живом сайте обычно торчат три способа что-то сделать «не из браузера»:</p>
<ul>
<li><code>/xmlrpc.php</code> — XML-RPC. Методы вроде <code>wp.getUsersBlogs</code>, <code>wp.newPost</code>, pingback. Клиент шлёт логин и пароль в теле XML на каждый запрос.</li>
<li><code>/wp-json/</code> — REST API. Gutenberg, приложение, ваш скрипт, куча плагинов. С WordPress 5.6 туда ходят Application Passwords через HTTP Basic Auth.</li>
<li><code>/wp-login.php</code> — человек в браузере. Сюда Application Password <em>не подходит</em>: это не замена пароля учётки для формы входа.</li>
</ul>
<p>Путаница начинается здесь. Человек включает 2FA и считает, что «без телефона никто не войдёт». Для формы логина — да. Для xmlrpc, если он принимает основной пароль пользователя, второго фактора нет. Бот не открывает браузер и не вводит код из приложения.</p>
<p>Вторая путаница: «раз есть Application Passwords, xmlrpc больше не нужен, WordPress сам его выключит». Не выключит. Это разные стеки. Пароль приложения, если xmlrpc жив, подходит и туда: в документации ядра прямо сказано, что Application Password можно подставить вместо обычного пароля в XML-RPC. То есть вы завели «безопасный» токен для REST, а старый файл его тоже съест.</p>
<p>Третья: галочка в плагине «Disable XML-RPC». Часто это фильтр <code>xmlrpc_enabled</code>. Он отключает методы, которым нужна авторизация. Файл при этом открывается, PHP крутится, pingback и часть методов без логина могут отвечать. Для бота, которому нужно занять php-fpm, этого достаточно. Для меня критерий «закрыто» — nginx/apache не отдают этот URI в PHP, а не «в админке галочка зелёная».</p>
<h5>Зачем xmlrpc вообще существует в 2026-м</h5>
<p>Когда-то это был нормальный способ писать с телефона и цеплять Jetpack. Сейчас на большинстве витрин, которые мне отдают, в access.log по <code>xmlrpc.php</code> нет ни одного своего User-Agent. Есть словари паролей, <code>system.multicall</code> и pingback на чужие сайты.</p>
<p>Две атаки, из-за которых я не оставляю файл «на всякий случай»:</p>
<ul>
<li>Брутфорс пачкой. <code>system.multicall</code> в одном HTTP-запросе прогоняет десятки <code>wp.getUsersBlogs</code> с разными паролями. Лимитер по запросам на IP видит один POST. Лимит без закрытия multicall — полумера: словарь укладывается в один хит.</li>
<li>Pingback как чужой молоток. Сайт принимает <code>pingback.ping</code> и сам идёт на URL, который подсунули. Либо участвует в отражённой нагрузке, либо светит внутренние адреса, если кто-то целится в SSRF. Даже без успешного логина файл уже не бесплатный.</li>
</ul>
<p>Jetpack, официальное приложение WordPress старых версий, часть сервисов рассылки и редкие десктопные клиенты xmlrpc ещё используют. Поэтому я не ставлю <code>deny all</code> вслепую на проде, пока не посмотрел неделю логов. Но «вдруг пригодится» — не причина держать вход, который умеет перебирать пароль администратора без 2FA.</p>
<h5>Что такое Application Passwords на практике</h5>
<p>С WordPress 5.6 у пользователя в профиле появляется блок «Пароли приложений». Вы даёте имя («backup-bot», «магазин-контент», «телефон редактора»), ядро показывает 24 символа один раз, дальше пароль лежит хешем в user meta. Пробелы в отображении можно не копировать — перед проверкой их срезают.</p>
<p>Зачем это, если «и так есть пароль админа»:</p>
<ul>
<li>Основной пароль не разъезжается по скриптам, телефону и подрядчику. Сменили пароль учётки после увольнения — бот с Application Password продолжает жить, пока его явно не отозвали. Это и плюс, и минус: ротация учётки дыру не закрывает.</li>
<li>Отзыв точечный. Удалили один пароль — отвалилось одно приложение, редактор в браузере не вылетел.</li>
<li>2FA на логине можно держать жёсткой. Скрипту второй фактор не нужен, человеку — нужен. Раньше из-за xmlrpc люди 2FA не включали: «приложение не зайдёт».</li>
<li>В профиле видно, когда пароль последний раз использовали и с какого IP. Не в реальном времени до секунды, с задержкой примерно до суток, но «висит полгода, last used — никогда» уже повод снести.</li>
</ul>
<p>Ходят они по HTTPS Basic Auth: логин учётки плюс этот пароль. Без HTTPS ядро по умолчанию блок даже не показывает: иначе пароль утечёт на первой же кофейне. Если сайт за прокси и WordPress думает, что он на http, в профиле пусто. Тогда сначала чинят <code>HTTPS</code>/<code>X-Forwarded-Proto</code>, а не «включают фильтр разрешить без SSL». Разрешить Application Passwords по http — сознательно оставить секрет в открытую на участке до прокси.</p>
<p>Права у такого пароля — права пользователя, не «только читать посты». Завели под администратором — получили администратора. Для заливки черновиков мне хватает роли автора или редактора и отдельной учётки, не «ещё один токен на admin».</p>
<h5>Как я смотрю, кто чем пользуется</h5>
<p>Пока нет картины за несколько дней, я ничего не отключаю на живом магазине. Сначала логи, потом решение.</p>
<p>Nginx, если access_log в обычном combined:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash"># пути к логам подставьте свои
awk '$7 ~ /xmlrpc\.php/ {print $1,$4,$9,$11}' /var/log/nginx/shop.example.access.log | tail -n 50
awk '$7 ~ /xmlrpc\.php/ {c[$1]++} END {for (i in c) print c[i], i}' /var/log/nginx/shop.example.access.log | sort -n | tail
awk '$7 ~ /wp-json\// {print $1,$9,$7}' /var/log/nginx/shop.example.access.log | awk '{print $3}' | sort | uniq -c | sort -n | tail</pre>
<p>Что я хочу увидеть:</p>
<ul>
<li>По xmlrpc: либо пусто, либо одни и те же /24 из сканеров, статус 200 на POST. Свой телефон обычно один-два IP, предсказуемый интервал, не 400 запросов в минуту.</li>
<li>User-Agent. Jetpack и приложение WordPress не маскируются под <code>Mozilla/5.0</code> с кривой кодировкой. Сканеры часто пишут <code>xmlrpc</code>, пусто или «WordPress» без версии.</li>
<li>По REST: Gutenberg с админки будет, это норма. Смотрю, есть ли POST/PUT с внешних IP на <code>/wp-json/wp/v2/posts</code> или media. Если есть — это живой клиент, его нельзя убить вместе с xmlrpc.</li>
</ul>
<p>Проверка «файл вообще отвечает», не из лога:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">curl -sS -D - -o /dev/null https://shop.example/xmlrpc.php
curl -sS https://shop.example/xmlrpc.php \
  -H 'Content-Type: text/xml' \
  -d '&lt;?xml version="1.0"?&gt;&lt;methodCall&gt;&lt;methodName&gt;system.listMethods&lt;/methodName&gt;&lt;/methodCall&gt;' | head</pre>
<p>200 и список методов — дверь открыта, даже если «плагин безопасности установлен». 403/404 с nginx без <code>X-Powered-By</code> и без тела PHP — уже похоже на правду.</p>
<p>REST и доступность Application Passwords:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">curl -sS https://shop.example/wp-json/ | python3 -m json.tool | head
# в корне смотрю ключ authentication.application-passwords
curl -sS -u 'editor:xxxx xxxx xxxx xxxx xxxx xxxx' \
  https://shop.example/wp-json/wp/v2/users/me</pre>
<p>Если в корне <code>authentication</code> пустой — либо HTTP, либо фича выключена фильтром, либо что-то по дороге срезает заголовок <code>Authorization</code>. Последнее на nginx с PHP-FPM встречается: CGI не пробрасывает Authorization, пока не сказать.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="nginx"># внутри location ~ \.php$
fastcgi_param HTTP_AUTHORIZATION $http_authorization;</pre>
<p>Без этой строки «пароль приложения не работает, а обычный логин в браузере живой» — не баг WordPress, а FPM не видит Basic Auth.</p>
<h5>Если xmlrpc никому не нужен — режу на веб-сервере</h5>
<p>Это мой дефолт для витрины без Jetpack и без старого мобильного клиента. Фильтр в теме — вторым слоем, не вместо.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="nginx"># server { ... }
location = /xmlrpc.php {
    access_log /var/log/nginx/shop.example.xmlrpc.denied.log;
    return 403;
}</pre>
<p><code>deny all</code> или <code>return 444</code> тоже работают. Мне удобнее 403 и отдельный лог: сразу видно, что сканеры не исчезли, просто больше не крутят PHP. На Apache в корне сайта или в конфиге хоста:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="apache">&lt;Files xmlrpc.php&gt;
    Require all denied
&lt;/Files&gt;</pre>
<p>Правило на WAF (если он есть) дублирует, не заменяет. Когда человек правит только Cloudflare и забывает origin, прямой IP сервера xmlrpc снова открыт.</p>
<p>После рестарта nginx проверяю тем же <code>curl</code>: не 200 со списком методов, а 403 без <code>text/html</code> от WordPress. Затем на час оставляю отдельный лог — если вдруг вылезет свой клиент, это будет видно по IP, а не «через неделю редактор звонит, что приложение молчит».</p>
<p>Плагины «выключить xmlrpc» я не ставлю как основную защиту. Лишний PHP на периметре, плюс половина из них делает только <code>xmlrpc_enabled</code>.</p>
<h5>Почему xmlrpc_enabled я не считаю закрытием</h5>
<p>В <code>functions.php</code> или mu-plugin часто копируют:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">add_filter( 'xmlrpc_enabled', '__return_false' );</pre>
<p>Это отключает авторизованные методы. Запрос всё равно доходит до WordPress: php-fpm воркер занят, плагины загрузились, объектный кеш дёрнули. Pingback на части сборок остаётся в списке. Сканер с <code>system.multicall</code> получает отказ, но уже после входа в PHP.</p>
<p>Если xmlrpc по какой-то причине оставляю, вычищаю хотя бы multicall и pingback:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php
// mu-plugins/disable-xmlrpc-extras.php
add_filter( 'xmlrpc_methods', function ( $methods ) {
    unset( $methods['system.multicall'], $methods['system.multitask'] );
    unset( $methods['pingback.ping'], $methods['pingback.extensions.getPingbacks'] );
    return $methods;
} );</pre>
<p>Это не «можно не закрывать файл». Это минимум, если Jetpack реально нужен и закрыть <code>location</code> нельзя. Плюс лимит запросов на этот URI отдельно от <code>admin-ajax.php</code> — иначе снова либо дыра, либо свои ловят 429.</p>
<h5>Когда xmlrpc оставляю</h5>
<p>Короткий список, не «все интеграции»:</p>
<ul>
<li>Jetpack и связанные сервисы Automattic, если они включены сознательно, а не «поставили и забыли».</li>
<li>Старое официальное приложение WordPress, которое ещё ходит в xmlrpc, а не в REST. Новые клиенты умеют Application Passwords и REST — их я перевожу.</li>
<li>Редкий десктопный клиент публикации, который заказчик не готов менять сегодня.</li>
</ul>
<p>Если оставляю, пароль приложения завожу под отдельным пользователем с нужной ролью, основной пароль этой учётки длинный и никуда не светится, 2FA на человеческих админах включена. xmlrpc тогда принимает Application Password, и утечка основного пароля админки не даёт боту этот вход. Обратное тоже верно: утечка пароля приложения — это вход в API с правами этой учётки, пока не отозвали.</p>
<p>Белый список IP на <code>xmlrpc.php</code> лучше, чем открытый мир. Для Jetpack это набор адресов Automattic, он плавает; без WAF с готовым правилом я не рисую его руками «по памяти» и не выкладываю чужие схемы. Практичнее: либо официальное правило у CDN, либо закрыть xmlrpc и жить на REST.</p>
<h5>Application Passwords: заводить, не размножать, уметь выключить</h5>
<p>Правила, без которых я токен не создаю:</p>
<ul>
<li>Одно приложение — один пароль. Не «один на все скрипты», чтобы потом не гадать, какой cron его светит.</li>
<li>Имя в профиле человеческое: <code>rsync-report bot</code>, <code>iPhone редактор</code>, не <code>test</code> и не <code>1</code>.</li>
<li>Учётка с минимальной ролью. Для черновиков не нужен администратор. Для медиа — часто хватает автора.</li>
<li>Пароль копирую в секрет хранилища один раз. В чат, в тикет и в тело статьи его не кладут. В логах nginx он не должен появиться: Basic Auth лучше не писать в access_log.</li>
<li>Раз в квартал смотрю last used. Не использовался — удаляю, не «вдруг пригодится».</li>
</ul>
<p>Через WP-CLI, если SSH есть:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">cd /var/www/shop.example/public_html
wp user list --fields=ID,user_login,roles
wp user application-password list editor --fields=uuid,name,created,last_used,last_ip
# создать и сразу забрать только секрет, без таблицы
wp user application-password create editor 'content-bot' --porcelain
# отозвать по uuid из списка
wp user application-password delete editor 00000000-0000-4000-8000-000000000000</pre>
<p>Если Application Passwords на сайте никто не использует (нет мобильного клиента, нет внешних скриптов, черновики пишут руками), я их выключаю. Иначе любой администратор может завести себе токен, а через дырявый плагин или XSS это делают уже не только администраторы.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php
// mu-plugins/disable-application-passwords.php
add_filter( 'wp_is_application_passwords_available', '__return_false' );</pre>
<p>Точечно, только выбранным ролям:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php">&lt;?php
add_filter( 'wp_is_application_passwords_available_for_user', function ( $allow, $user ) {
    if ( $user instanceof WP_User &amp;&amp; in_array( 'administrator', (array) $user-&gt;roles, true ) ) {
        return $allow;
    }
    return false;
}, 10, 2 );</pre>
<p>Выключить и забыть отозвать уже выданные пароли — мало. Фильтр закрывает новые и использование, но я всё равно прохожусь по списку и удаляю старые: меньше сюрпризов, когда фильтр кто-то снимет «чтобы приложение заработало».</p>
<h5>REST я не вырубаю «за компанию»</h5>
<p>После статьи про xmlrpc находится человек, который в плагине безопасности ставит Disable REST API. Gutenberg после этого рисует белый экран, кастомайзер полуживой, часть блоков не сохраняется. REST — не xmlrpc. Это штатный транспорт админки.</p>
<p>Что я ограничиваю, не выключая REST целиком:</p>
<ul>
<li>Публичный список пользователей. На многих сайтах <code>GET /wp-json/wp/v2/users</code> без авторизации отдаёт логины. Для брутфорса это подарок: не надо угадывать <code>admin</code>. Плагин безопасности или маленький mu-plugin, который на <code>rest_authentication_errors</code> закрывает <code>users</code> для анонимов, — нормальная гигиена. Слепой «REST off» — нет.</li>
<li>Лимит на <code>/wp-json/</code> на краю, отдельно от html, если вижу перебор. Не общий <code>location ~ \.php$</code>.</li>
<li>Не торчит ли создание Application Passwords для всех ролей, если фича нужна только админу.</li>
</ul>
<p>Проверка, светит ли сайт логины:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">curl -sS 'https://shop.example/wp-json/wp/v2/users' | python3 -m json.tool | head</pre>
<p>Если в ответе массив с <code>slug</code> — аноним уже знает, кого подбирать. Это не «дыра нулевого дня», это дефолт, который я закрываю в тот же вечер, что и xmlrpc.</p>
<h5>Какие комбинации я считаю нормальными</h5>
<p>Свожу к трём схемам, без зоопарка галочек.</p>
<ol>
<li>Витрина, правки из браузера, нет Jetpack, нет внешних ботов. xmlrpc на nginx 403. Application Passwords выключены фильтром. 2FA на администраторах. REST живой, список users для гостя закрыт. Это большинство сайтов, которые мне отдают «просто посмотрите безопасность».</li>
<li>Нужен скрипт или телефон. xmlrpc всё равно 403, если клиент умеет REST. Одна учётка с ролью по задаче, один Application Password, HTTPS доведён до ядра, Authorization проброшен в FPM. Основной пароль этой учётки не совпадает с паролем приложения и никуда не записан в cron.</li>
<li>Jetpack или старый клиент, без них пока никак. xmlrpc открыт, multicall и pingback сняты, лимит на URI, по возможности ограничение по источнику на WAF. Пароли приложений для этого клиента, не основной пароль админа. Остальным пользователям Application Passwords не светятся.</li>
</ol>
<p>Схема, которую я снимаю сразу: 2FA «для галочки», xmlrpc открыт на мир, в cron лежит основной пароль администратора, в профиле три пароля приложения с именами test/test2/asdf, REST отдаёт users. Это не «чуть-чуть не донастроили». Это три входа с одним секретом и одним без второго фактора.</p>
<h5>Что проверить за ночь, а не сразу закрыть ноутбук</h5>
<p>После изменений я не верю одному <code>curl</code> с ноутбука. За ночь видно, не отвалился ли свой клиент и не начал ли сканер долбить соседнюю дверь.</p>
<ul>
<li>Отдельный лог 403 по xmlrpc: есть чужие POST, нет 200 в php-fpm slowlog по этому файлу.</li>
<li>В access.log нет всплеска 401/403 на <code>/wp-json/wp/v2/</code> с офисного IP. Если есть — вы закрыли не то, или срезали Authorization.</li>
<li>Админка открывается, запись сохраняется, медиа грузится. Gutenberg ходит в REST; если его прибили, узнаете утром от редактора, не из мониторинга.</li>
<li><code>wp user application-password list</code> по всем, у кого не пусто: имена понятные, last_ip свой, лишние удалены.</li>
<li>Повторный <code>curl</code> на <code>/wp-json/wp/v2/users</code> без куки — не список логинов.</li>
<li>Плагин безопасности не включил обратно xmlrpc своим «режимом совместимости» и не выключил REST. После обновления плагинов это проверяю отдельно.</li>
</ul>
<p>Если за ночь в xmlrpc-логе вдруг свой IP приложения — не открываю файл обратно «как было», а перевожу клиент на REST и Application Password. Старый xmlrpc ради одного телефона на витрине с заказами я оставляю только когда перевод реально нельзя сделать сегодня, и тогда это временная схема с датой, а не новая норма.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/vebmasteru/application-passwords-vs-xml-rpc-chto-ostavlyat-i-kak-ne-otkryt-lishnjuju-dyru/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Миграция домена WordPress без поломки serialized-данных</title>
		<link>https://remadmin.com/blog/vebmasteru/migraciya-domena-wordpress-bez-polomki-serialized-dannyh/</link>
					<comments>https://remadmin.com/blog/vebmasteru/migraciya-domena-wordpress-bez-polomki-serialized-dannyh/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Tue, 22 Sep 2026 20:29:06 +0000</pubDate>
				<category><![CDATA[Вебмастеру]]></category>
		<category><![CDATA[Mysql]]></category>
		<category><![CDATA[Wordpress]]></category>
		<category><![CDATA[wp-cli]]></category>
		<category><![CDATA[миграция]]></category>
		<category><![CDATA[Резервное копирование]]></category>
		<category><![CDATA[шпаргалка]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7426</guid>

					<description><![CDATA[Почему REPLACE по дампу оставляет рабочий siteurl и мёртвые виджеты. Как я меняю домен через WP-CLI с учётом сериализации, какие варианты URL не забыть и что проверить за ночь.]]></description>
										<content:encoded><![CDATA[<p>Типичная заявка: «переехали на новый домен, главная открывается, в админку пускает, а меню пустое, виджеты слетели, Elementor открывается белым экраном, в кастомайзере ошибка». Иногда мягче: «часть картинок с старого адреса, часть с нового», «письма клиентам уходят со старой ссылкой», «после смены домена пропал футер».</p>
<p>Я почти всегда нахожу одно и то же. Кто-то сделал в phpMyAdmin <code>REPLACE</code> по <code>option_value</code> или прогнал дамп через «найти и заменить» в редакторе. Поля <code>siteurl</code> и <code>home</code> в <code>wp_options</code> — обычные строки, они меняются и сайт «как будто живой». Виджеты, theme_mods, ACF, настройки кэша и куча plugin-опций лежат как PHP-сериализация. Наивная замена портит длину строк внутри этой каши. <code>unserialize()</code> возвращает <code>false</code>, WordPress считает «настроек нет» и рисует пустые сайдбары.</p>
<p>Ниже — как я перевожу домен, чтобы не чинить потом сериализацию руками. Хосты в примерах вымышленные: <code>old.example</code> → <code>shop.example</code>. Свои домены, префикс таблиц и путь к сайту подставляйте, чужой дамп из интернета на прод не накатывайте.</p>
<h5>Почему обычный REPLACE ломает сайт</h5>
<p>PHP пишет строку не как «текст», а как «длина плюс текст». Упрощённо:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">s:19:"https://old.example";</pre>
<p>«19» — это число байт внутри кавычек, не «примерно длина». Если в дампе или SQL вы меняете <code>old.example</code> на <code>shop.example</code>, букв становится больше, а префикс <code>s:19:</code> остаётся. Для интерпретатора это уже не валидная сериализация. Массив вокруг тоже разваливается: один битый кусок — и весь option «не читается».</p>
<p>Отсюда картина, которую путают с «плагин не перенёсся»:</p>
<ul>
<li>Админка работает: логин, список записей, даже медиатека. Это не сериализация, это обычные таблицы.</li>
<li>Виджеты пустые или «сбросились на дефолт». Классика — <code>sidebars_widgets</code> и <code>widget_*</code>.</li>
<li>Кастомайзер/меню выглядят как новый сайт на чистой теме: <code>theme_mods_*</code> не распаковался.</li>
<li>Страница открывается, визуальный редактор — нет. У конструкторов URL ещё и в JSON/CSS, не только в serialize.</li>
<li>В PHP-логах за ночь: <code>unserialize(): Error at offset …</code> на <code>option_value</code> или postmeta. Это не «хостер глючит», это вы вчера поправили домен в БД.</li>
</ul>
<p><code>siteurl</code>/<code>home</code> я вообще не считаю критерием успеха. Их можно поменять руками за минуту. Критерий — живые виджеты, меню, конструктор и отсутствие старого хоста в опциях, которые WordPress реально читает.</p>
<h5>Что я снимаю до любой замены</h5>
<p>Пока не лежит откат, я замену не запускаю. Испорченная сериализация откатывается из дампа за две минуты и чинится «на глаз» полдня.</p>
<p>Минимум:</p>
<ul>
<li>Дамп базы <em>до</em> замены, не «сейчас сниму, если что». И файл не в <code>wp-content/uploads</code> этого же сайта.</li>
<li>Архив файлов: тема, <code>uploads</code>, <code>mu-plugins</code>. Конструкторы после смены домена часто оставляют CSS со старым хостом уже в файлах, не в MySQL.</li>
<li>Текущие <code>siteurl</code> и <code>home</code>, плюс константы из <code>wp-config.php</code>. Если заданы <code>WP_HOME</code>/<code>WP_SITEURL</code>, база может врать, а сайт всё равно ходить на старый адрес — или наоборот, «замена в БД не действует».</li>
<li>Список вариантов URL, которые реально встречаются. Не один «красивый» https.</li>
</ul>
<p>Варианты, которые я выписываю в блокнот до команды, не в процессе:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">https://old.example
http://old.example
https://www.old.example
http://www.old.example
//old.example
old.example</pre>
<p>Слеш на конце — отдельная история. В опциях чаще без него, в контенте и в JSON конструктора бывает с ним. Менять «с слэшем» и «без» двумя проходами безопаснее, чем надеяться, что один проход поймает оба.</p>
<p>Голый <code>old.example</code> без схемы я оставляю на последний проход и смотрю отчёт: он может задеть e-mail вида <code>shop@old.example</code>, CDN, упоминания в тексте. Если почта на этом домене остаётся — голый хост не трогаю или меняю точечно.</p>
<p>Префикс таблиц не угадываю:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash"># каталог сайта подставьте свой
cd /var/www/example-site/public_html
wp config get table_prefix
wp option get siteurl
wp option get home
wp config get WP_HOME
wp config get WP_SITEURL</pre>
<p>Если две последние константы заданы и расходятся с тем, куда вы переезжаете, сначала правлю <code>wp-config.php</code>. Иначе search-replace в базе вы сделаете, а WordPress продолжит считать каноническим старый URL.</p>
<h5>WP-CLI: основной путь</h5>
<p>На VPS и нормальном хостинге с SSH я не открываю phpMyAdmin для этой задачи. <code>wp search-replace</code> ходит по ячейкам, понимает PHP-сериализацию (и в свежих версиях — JSON), первичные ключи не трогает.</p>
<p>Сначала сухой прогон, чтобы увидеть масштаб, а не «сразу на прод»:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">cd /var/www/example-site/public_html

wp search-replace 'https://old.example' 'https://shop.example' \
  --all-tables-with-prefix \
  --precise \
  --dry-run \
  --report-changed-only</pre>
<p><code>--all-tables-with-prefix</code> нужен потому, что дефолт — таблицы, которые WordPress зарегистрировал в <code>$wpdb</code>. У Magento-в-WP это не про нас, а у WooCommerce, форм, кэша, самописных плагинов таблица <code>wp_something_extra</code> легко остаётся со старым хостом. <code>--all-tables</code> без префикса я на шареде с общей MySQL не включаю: можно вылезти в чужую базу на том же пользователе.</p>
<p><code>--precise</code> гоняет замену через PHP, не через быстрый SQL. Медленнее, зато меньше шансов проскочить колонку, где сериализация не похожа на «очевидную». На магазине в сотни мегабайт дампа это не мгновенно — зато не потом ночью ловить пустую корзину.</p>
<p>Если сухой прогон выглядит здраво (есть попадания в <code>wp_options</code>, postmeta, может быть post content — и нет сюрприза в <code>wp_users.user_email</code> на тысячи строк), снимаю ещё один свежий дамп и запускаю без <code>--dry-run</code>.</p>
<p>GUID записей я обычно пропускаю:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">wp search-replace 'https://old.example' 'https://shop.example' \
  --all-tables-with-prefix \
  --precise \
  --skip-columns=guid \
  --report-changed-only</pre>
<p><code>guid</code> в WordPress — идентификатор, не «красивая ссылка». Менять его при переезде домена не обязательно, а фиды и импорты от этого иногда едут. Если у вас старый зоопарк, где в GUID руками писали permalink — смотрите отчёт dry-run по колонке и решайте точечно, не «всем REPLACE».</p>
<p>Дальше те же команды для <code>http://</code>, <code>www</code> и протоколо-относительного <code>//old.example</code>. Один проход по «основному» https почти никогда не вычищает всё: редирект на https могли включить год назад, а в виджете 2019 года лежит http.</p>
<p>Если WP-CLI есть на старом сервере, а на новом ещё нет, удобен экспорт уже с заменой:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">wp search-replace 'https://old.example' 'https://shop.example' \
  --all-tables-with-prefix \
  --precise \
  --skip-columns=guid \
  --export=/root/shop-example-after-replace.sql</pre>
<p>Накатываю этот SQL на новое место, а не «сырой дамп плюс sed». <code>sed</code> по <code>.sql</code> — тот же класс ошибки, что REPLACE в phpMyAdmin, только в файле на 400 МБ её ещё и не видно.</p>
<h5>Если WP-CLI нет</h5>
<p>На дешёвом shared без SSH люди ставят плагин вроде Better Search Replace. Под капотом та же идея, что у interconnect/it: идти по ячейкам и пересобирать serialize. Это лучше, чем правка дампа. Это хуже, чем WP-CLI, потому что плагин работает из-под веба, на больших таблицах упирается в timeout, и его ещё надо не забыть удалить.</p>
<p>Правила, без которых я этот путь не рекомендую:</p>
<ul>
<li>Сначала дамп. Плагин «умеет dry-run» — dry-run обязателен, как у CLI.</li>
<li>Ставить, прогнать, снести. Search-Replace, оставленный в корне сайта, — готовая дыра: это скрипт с доступом к базе, который не должен торчать в интернет ни минуты после работы.</li>
<li>Не мешать в одном заходе «домен» и «путь на диске». Это разные строки. Путь <code>/home/u1234/public_html</code> в сериализации тоже бывает, его меняют отдельно, когда переезд ещё и на другой аккаунт хостинга.</li>
</ul>
<p>Перенос «архивом плагина миграции» (Duplicator и родственники) сериализацию обычно переживает, потому что замену они делают своим движком, не вашим REPLACE. Имеет смысл, когда нет SSH. Не имеет смысла, когда вы уже на VPS и можете одну команду. Я не тащу лишний плагин на прод ради смены домена.</p>
<h5>Что search-replace не видит</h5>
<p>База — не весь сайт. После удачной замены в MySQL у меня регулярно всплывает старый хост в файлах.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">cd /var/www/example-site/public_html
grep -R --binary-files=without-match -l 'old.example' \
  wp-content/themes wp-content/plugins wp-content/mu-plugins \
  wp-content/uploads wp-config.php 2&gt;/dev/null | head</pre>
<p>Типичные находки:</p>
<ul>
<li><code>WP_HOME</code>/<code>WP_SITEURL</code> в <code>wp-config.php</code> — перекрывают базу, спор «я же заменил в опциях» решается здесь.</li>
<li>CSS/JSON конструктора в <code>wp-content/uploads/elementor/</code> (и аналоги у других билдеров). Это файлы. Их WP-CLI не трогает. У Elementor после смены домена я ещё регенерирую CSS из админки или, если есть их CLI, отдельной командой замены URL, не надеясь только на MySQL.</li>
<li>Жёстко прошитый адрес в дочерней теме: логотип, пиксель, кастомный endpoint. Это уже код, не контент.</li>
<li>Объектный кэш. Redis/Memcached продолжает отдавать старые сериализованные blobs. После замены — flush. Иначе «в базе уже новый домен, в браузере старый» до истечения TTL, и вы начнёте чинить то, что уже починено.</li>
</ul>
<p>Мультисайт — не тот же чеклист. Там <code>wp_blogs</code>, <code>wp_site</code>, домены блогов, и CLI нужно звать с <code>--url</code> конкретного сайта плюс <code>--network</code>, когда меняете сразу сеть. Если у вас не мультисайт, не копируйте чужие однострочники с <code>--network</code> «на всякий случай».</p>
<h5>Как понять, что сериализация жива</h5>
<p>Первая проверка — не «главная открылась». Я смотрю то, что сериализация обычно убивает, и то, что REPLACE обычно забывает.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash"># канонические URL — уже новый домен, без редиректа в голове
wp option get siteurl
wp option get home

# остатки старого хоста по всем таблицам с префиксом
wp db search 'old.example' --all-tables-with-prefix --table_column_once

# виджеты реально читаются, не «таблица на месте»
wp widget list sidebar-1
wp option get sidebars_widgets</pre>
<p>Если <code>sidebars_widgets</code> печатает массив с именами сайдбаров — сериализация этого option жива. Если пусто, <code>false</code> или PHP Notice на unserialize — откатились к дампу, не «восстанавливаем виджеты руками».</p>
<p>Грубый проход по опциям, которые WordPress считает сериализованными, но уже не может распаковать:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">wp eval '
foreach ( wp_load_alloptions() as $name =&gt; $value ) {
  if ( ! is_serialized( $value ) ) { continue; }
  $restored = @unserialize( $value );
  if ( $value === "b:0;" || $restored !== false || $value === serialize( false ) ) { continue; }
  echo $name, PHP_EOL;
}
'</pre>
<p>Пустой вывод — хороший знак. Имена вроде <code>widget_block</code>, <code>theme_mods_…</code>, <code>external_updates</code> в этом списке — плохой: именно они после «успешного» REPLACE приезжают битыми.</p>
<p>В админке руками:</p>
<ul>
<li>Внешний вид → Виджеты / редактор блоков виджетов: блоки на месте, не пустой холст.</li>
<li>Меню и расположения меню. «Меню существует, но нигде не назначено» часто значит, что theme_mods не прочитался.</li>
<li>Медиа: открыть пару старых записей, не главную. В контенте <code>https://old.example/wp-content/uploads/…</code> после нормальной замены быть не должно.</li>
<li>Конструктор: открыть на редактирование страницу, которую точно верстали не вчера. Белый экран при живой публичной странице — почти всегда JSON/CSS со старым хостом или битая meta.</li>
</ul>
<p>Смешанный контент (главная по https, картинки по http старого домена) я проверяю не глазами, а по исходнику и по логам. Браузер может дорисовать из кэша.</p>
<h5>Что смотреть overnight</h5>
<p>Днём вы видите то, что кликнули. Ночью всплывает cron, письма и боты.</p>
<p>За ночь мне нужны:</p>
<ul>
<li>Письма сайта: сброс пароля, WooCommerce, формы. В теле и в заголовке <code>From</code>/ссылках не должен торчать старый хост. Это часто не <code>siteurl</code>, а отдельная опция плагина или константа SMTP.</li>
<li>Cron: <code>wp cron event list</code> и системный crontab. Старый URL в HTTP-cron (wget на <code>wp-cron.php</code>) продолжает дергать прежний домен, пока DNS жив.</li>
<li>Карта сайта и robots: плагины SEO пишут абсолютные URL. Rank Math/Yoast после переезда я открываю и пересохраняю карту, не надеясь, что «само подхватит».</li>
<li>Редирект со старого домена. Пока он не 301 на тот же путь нового, Google и закладки клиентов будут кормить оба хоста. Сам search-replace редирект не настраивает — это nginx/хостер.</li>
<li>Логи PHP на unserialize и на mixed content в error_log. Одна-две строки offset — повод не «подождать», а свернуть на дамп и прогнать замену нормально.</li>
<li>REST и превью: <code>/wp-json/</code> должен отдавать новый <code>home</code> в <code>url</code>/<code>home</code>. Иначе мобильные приложения и часть плагинов продолжают стучать на старое имя.</li>
</ul>
<p>Если старый домен ещё резолвится в тот же DocumentRoot без редиректа, вы будете неделю ловить «то новый, то старый» в кэше и в сериализации, которую кто-то снова сохранит из админки, зайдя по старому имени. Схема одна: один канонический хост, второй только 301, константы и опции смотрят на канон.</p>
<p>Переезд домена в WordPress — это не «поменять две опции в wp_options». Две опции как раз переживают любой REPLACE. Ломается то, что вы не видите в phpMyAdmin с первого экрана: длины строк внутри serialize. Я меняю URL инструментом, который эти длины пересчитывает, потом отдельно ищу хвосты в файлах и кэше, и только после живых виджетов и пустого поиска по старому хосту считаю переезд состоявшимся — не в момент, когда открылась главная.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/vebmasteru/migraciya-domena-wordpress-bez-polomki-serialized-dannyh/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Как безопасно удалить пользователя в Ubuntu Linux</title>
		<link>https://remadmin.com/blog/linux/kak-bezopasno-udalit-polzovatelya-v-ubuntu-linux/</link>
					<comments>https://remadmin.com/blog/linux/kak-bezopasno-udalit-polzovatelya-v-ubuntu-linux/#respond</comments>
		
		<dc:creator><![CDATA[REMADMIN]]></dc:creator>
		<pubDate>Thu, 29 Feb 2024 09:16:30 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[шпаргалка]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7289</guid>

					<description><![CDATA[Умение эффективно удалять пользователей является ключевым навыком для специалистов в области системного администрирования. Устаревшие учетные записи могут представлять угрозу для безопасности сервера, поэтому их следует регулярно удалять. В данной статье мы предоставим информацию о процессе удаления пользователей в Linux Ubuntu и расскажем о необходимых шагах перед выполнением этой операции, чтобы избежать негативных последствий для системы. [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Умение эффективно удалять пользователей является ключевым навыком для специалистов в области системного администрирования. Устаревшие учетные записи могут представлять угрозу для безопасности сервера, поэтому их следует регулярно удалять. В данной статье мы предоставим информацию о процессе удаления пользователей в Linux Ubuntu и расскажем о необходимых шагах перед выполнением этой операции, чтобы избежать негативных последствий для системы.</p>
<h6>Проверяем, авторизован ли пользователь на сервере, и есть ли запущенные от него процессы</h6>
<pre class="EnlighterJSRAW" data-enlighter-language="powershell">who
sudo ps -u username</pre>
<h6>Убиваем процессы пользователя, если они есть</h6>
<pre class="EnlighterJSRAW" data-enlighter-language="powershell">sudo killall -9 -u username</pre>
<h6>Отключаем все задачи планировщика cron этого пользователя</h6>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">sudo crontab -r -u username</pre>
<h6>Собственно удаляем пользователя</h6>
<p>Если нужно удалить пользователя, но оставить его домашнюю директорию, то выполняем:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="powershell">sudo deluser username</pre>
<p>Если нужно удалить всё, включая и его домашнюю директорию, то выполняем команду:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">sudo deluser --remove-home username</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/linux/kak-bezopasno-udalit-polzovatelya-v-ubuntu-linux/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Поиск в файлах по содержимому в Linux</title>
		<link>https://remadmin.com/blog/linux/poisk-v-fajlah-po-soderzhimomu-v-linux/</link>
					<comments>https://remadmin.com/blog/linux/poisk-v-fajlah-po-soderzhimomu-v-linux/#respond</comments>
		
		<dc:creator><![CDATA[REMADMIN]]></dc:creator>
		<pubDate>Wed, 28 Feb 2024 13:51:01 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[шпаргалка]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7301</guid>

					<description><![CDATA[Linux, как операционная система с открытым исходным кодом, обеспечивает пользователей мощными инструментами для работы с файловой системой. Один из ключевых аспектов этой работы &#8211; поиск файлов по их содержимому. В данной статье мы рассмотрим различные методы и инструменты для проведения эффективного поиска в файлах по их содержимому в Linux. Команда grep grep является одной из [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Linux, как операционная система с открытым исходным кодом, обеспечивает пользователей мощными инструментами для работы с файловой системой. Один из ключевых аспектов этой работы &#8211; поиск файлов по их содержимому. В данной статье мы рассмотрим различные методы и инструменты для проведения эффективного поиска в файлах по их содержимому в Linux.</p>
<h5>Команда grep</h5>
<p><strong>grep</strong> является одной из наиболее популярных и мощных утилит для поиска текстовых данных в файлах. Команда <strong>grep</strong> позволяет указать строку или регулярное выражение для поиска и применяется следующим образом:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">grep "ключевое_слово" /путь/к/файлу.txt</pre>
<p>Для рекурсивного поиска в каталогах вы можете использовать опцию <strong>-r</strong>, позволяющую искать вложенные файлы и подкаталоги. Пример:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">grep -r "искомый_текст" /путь/к/каталогу</pre>
<h5>find и xargs совместно</h5>
<p>Комбинация команд <strong>find</strong> и <strong>xargs</strong> позволяет находить файлы с заданными критериями и передавать их в качестве аргументов для других команд. Пример:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">find /путь -type f -exec grep "искомый_текст" {} +</pre>
<p>В данном примере <strong>find</strong> находит все файлы (-type f) в указанном пути и передает их в <strong>grep</strong>, где производится поиск заданного текста.</p>
<h5>find и grep совместно</h5>
<p>Комбинирование <strong>find</strong> и <strong>grep</strong> позволяет более тонко настраивать поиск. Например, поиск в файлах, измененных за последние 7 дней:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">find /путь -type f -mtime -7 -exec grep "искомый_текст" {} +</pre>
<p>Поиск в файлах по содержимому в Linux может быть выполнен различными способами, и выбор зависит от конкретной задачи. Описанные инструменты предоставляют гибкость и мощь для проведения поиска в текстовых файлах, а их комбинация может быть использована для достижения оптимальных результатов. Учитывайте особенности каждого инструмента и выбирайте подходящий в зависимости от контекста использования.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/linux/poisk-v-fajlah-po-soderzhimomu-v-linux/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Руководство по работе с планировщиком задач cron</title>
		<link>https://remadmin.com/blog/linux/rukovodstvo-po-rabote-s-planirovshhikom-zadach-cron/</link>
					<comments>https://remadmin.com/blog/linux/rukovodstvo-po-rabote-s-planirovshhikom-zadach-cron/#respond</comments>
		
		<dc:creator><![CDATA[REMADMIN]]></dc:creator>
		<pubDate>Mon, 26 Feb 2024 11:57:07 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[cron]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[шпаргалка]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7298</guid>

					<description><![CDATA[Linux, как операционная система, предоставляет множество инструментов для автоматизации различных задач. Один из таких инструментов — планировщик задач cron. В этой статье мы рассмотрим, как эффективно использовать планировщик в Linux для автоматизации рутинных операций. Что такое cron? Cron — это стандартный инструмент в Unix-подобных системах, предназначенный для выполнения задач в установленное время или периодически. Он [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Linux, как операционная система, предоставляет множество инструментов для автоматизации различных задач. Один из таких инструментов — планировщик задач cron. В этой статье мы рассмотрим, как эффективно использовать планировщик в Linux для автоматизации рутинных операций.</p>
<h5>Что такое cron?</h5>
<p>Cron — это стандартный инструмент в Unix-подобных системах, предназначенный для выполнения задач в установленное время или периодически. Он основан на файлах cron, в которых определены задания и их расписание. Эти файлы хранятся в каталоге <strong>/etc/cron.d/</strong> или <strong>/var/spool/cron/</strong> и обычно недоступны для редактирования напрямую.</p>
<h5>Работа с cron</h5>
<h6>Создание и редактирование cron-задач</h6>
<p>Для создания новой задачи, или редактирования существующих, используется команда:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">crontab -e</pre>
<p>Она открывает текстовый редактор, в котором можно определить расписание и команду для выполнения. Например, чтобы запустить скрипт &#8220;myscript.sh&#8221; каждый день в 2 часа ночи, добавьте следующую строку:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">0 2 * * * /путь/к/скрипту/myscript.sh</pre>
<p>Редактировать команды другого пользователя можно так:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">sudo crontab -u username -e</pre>
<h6>Листинг задач</h6>
<p>Список текущих cron-задач можно просмотреть с помощью команды:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">crontab -l</pre>
<p>Это полезно для проверки текущего расписания и быстрого определения того, что уже настроено.</p>
<p>Вывести задачи другого пользователя можно так:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">sudo crontab -u username -l</pre>
<h6>Удаление задач</h6>
<p>Эта команда удаляет все cron-задачи пользователя:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">crontab -r</pre>
<p>Если необходимо удалить конкретную задачу, используйте:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">crontab -l</pre>
<p>чтобы увидеть ее номер, а затем:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">crontab -r номер</pre>
<h5>Вывод результата в лог-файл</h5>
<p>Для записи результатов выполнения задачи в лог-файл, вы можете использовать перенаправление стандартного вывода и стандартной ошибки в файл прямо внутри команды cron. Вот пример:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">* * * * * /путь/к/вашему/скрипту.sh &gt;&gt; /путь/к/лог-файлу.log 2&gt;&amp;1</pre>
<p>Здесь <strong>&gt;&gt;</strong> используется для добавления вывода в конец файла, <strong>2&gt;&amp;1</strong> перенаправляет стандартные ошибки в стандартный вывод, чтобы они также попадали в лог-файл.</p>
<h5>Указание времени выполнения задач</h5>
<p>В планировщике задач cron в Linux время выполнения задач задается в виде пяти полей, каждый из которых представляет собой определенный аспект времени. Эти поля определяют минуты, часы, дни месяца, месяцы и дни недели. Вот структура этих полей:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">* * * * *
- - - - -
| | | | |
| | | | +----- День недели (0 - воскресенье, 1 - понедельник, ..., 6 - суббота)
| | | +------- Месяц (1 - январь, 2 - февраль, ..., 12 - декабрь)
| | +--------- День месяца (1 - 31)
| +----------- Час (0 - 23)
+------------- Минуты (0 - 59)</pre>
<p>Каждое поле может принимать определенные значения или диапазоны значений, а также знаки &#8220;*&#8221; (звездочка) и &#8220;/&#8221; (косая черта).</p>
<ul>
<li><code>*</code>: Означает &#8220;каждый&#8221;. Например, если в поле минут стоит &#8220;*&#8221;, это означает, что задача будет выполняться каждую минуту.</li>
<li><code>число</code>: Определенное значение. Например, если в поле часов стоит &#8220;3&#8221;, задача будет выполняться в 3 часа.</li>
<li><code>число-число</code>: Диапазон значений. Например, &#8220;1-5&#8221; в поле дней недели означает с понедельника по пятницу.</li>
<li><code>*/число</code>: Каждый определенный интервал. Например, &#8220;*/15&#8221; в поле минут означает каждые 15 минут.</li>
</ul>
<h6>Примеры:</h6>
<p>Запуск задачи каждый день в 3 часа утра:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">0 3 * * *</pre>
<p>Запуск задачи каждый понедельник и среду в 12:30:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">30 12 * * 1,3</pre>
<p>Запуск задачи каждый час:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">0 * * * *</pre>
<p>Запуск задачи каждый день в 5 утра и 8 вечера:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">0 5,20 * * *</pre>
<p>Таким образом, правильное указание времени выполнения задачи в cron позволяет вам гибко управлять ее расписанием, с учетом требований вашего проекта или системы.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/linux/rukovodstvo-po-rabote-s-planirovshhikom-zadach-cron/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Как найти большие папки и файлы в Linux</title>
		<link>https://remadmin.com/blog/linux/kak-najti-bolshie-papki-i-fajly-v-linux/</link>
					<comments>https://remadmin.com/blog/linux/kak-najti-bolshie-papki-i-fajly-v-linux/#respond</comments>
		
		<dc:creator><![CDATA[REMADMIN]]></dc:creator>
		<pubDate>Sun, 25 Feb 2024 10:44:11 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[шпаргалка]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7294</guid>

					<description><![CDATA[Для поиска больших файлов и папок в Linux с последующей сортировкой результатов, можно использовать комбинацию команд find, du, и sort. Вот несколько примеров: Поиск больших файлов с сортировкой по убыванию, с выводом в терминал только 20 больших файлов,  в указанной папке find /путь/к/каталогу -type f -size +100M -exec du -h {} + &#124; sort -rh [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Для поиска больших файлов и папок в Linux с последующей сортировкой результатов, можно использовать комбинацию команд find, du, и sort. <span id="more-7294"></span>Вот несколько примеров:</p>
<h6>Поиск больших файлов с сортировкой по убыванию, с выводом в терминал только 20 больших файлов,  в указанной папке</h6>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">find /путь/к/каталогу -type f -size +100M -exec du -h {} + | sort -rh | head -n 20</pre>
<p>Эта команда ищет файлы размером более 100 мегабайт в указанном каталоге и его подкаталогах, затем сортирует результаты по размеру в убывающем порядке.</p>
<h6>Поиск больших файлов с сортировкой по убыванию в текущей папке</h6>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">find . -type f -size +500M -exec du -h {} + | sort -rh</pre>
<p>Эта команда ищет файлы размером более 500 мегабайт в текущем каталоге и его подкаталогах, затем сортирует результаты по размеру в убывающем порядке.</p>
<h6>Поиск больших папок с сортировкой по убыванию и выводом в терминал только 20 самых больших папок</h6>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">du -h --max-depth=1 /путь/к/каталогу | sort -rh | head -n 20</pre>
<p>Эта команда выводит размер каждой папки в указанном каталоге (включая подкаталоги) и сортирует результаты по размеру в убывающем порядке.</p>
<p><strong>Поиск папок, содержащих большое количество файлов, с сортировкой по убыванию, и выводом только 20 таких папок</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">find /путь/к/каталогу -type d -exec sh -c 'echo -n "{} " &amp;&amp; find "{}" -maxdepth 1 -type f | wc -l' \; | sort -k2 -nr | head -n 20</pre>
<p>Эта команда ищет все подкаталоги в указанном каталоге, выводит каждый подкаталог вместе с количеством файлов в нем, а затем сортирует результаты по количеству файлов в убывающем порядке.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/linux/kak-najti-bolshie-papki-i-fajly-v-linux/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SSH &#8211; проброс приватного ключа</title>
		<link>https://remadmin.com/blog/linux/ssh-probros-privatnogo-kljucha/</link>
					<comments>https://remadmin.com/blog/linux/ssh-probros-privatnogo-kljucha/#respond</comments>
		
		<dc:creator><![CDATA[REMADMIN]]></dc:creator>
		<pubDate>Sat, 24 Feb 2024 07:56:04 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[шпаргалка]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7283</guid>

					<description><![CDATA[Проброс приватного ключа SSH позволяет, будучи подключённым к одному удалённому серверу, подключаться к другим, используя один и тот же приватный ключ, находящийся на вашем компьютере. Функция может быть очень полезна, если вы, например, работаете с GIT, прямо на удалённом сервере и вам нужно отправить с него же правки в GitHub. Для проброса приватного ключа используется [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Проброс приватного ключа SSH позволяет, будучи подключённым к одному удалённому серверу, подключаться к другим, используя один и тот же приватный ключ, находящийся на вашем компьютере. Функция может быть очень полезна, если вы, например, работаете с GIT, прямо на удалённом сервере и вам нужно отправить с него же правки в GitHub.</p>
<p>Для проброса приватного ключа используется <strong>SSH Агент. </strong>Способ одинаково работает в консоли любых операционных систем Linux, macOS и Windows.</p>
<p>Первым делом проверяем, запущен ли агент, и знает ли он что-то о вашем ключе:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="powershell">ssh-add -l</pre>
<p>Если получаем ответ: <strong>Could not open a connection to your authentication agent, </strong>запускаем агент командой:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="powershell">eval 'ssh-agent'</pre>
<p>Если ответ: <strong>The agent has no identities, </strong>значит нужно сообщить ему о вашем приватном ключе, выполнив команду:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="powershell">ssh-add</pre>
<p>Если в ответе команды <strong>ssh-add -l </strong>вы видите путь к своему приватному ключу, значит всё хорошо, и вы можете подключаться к удалённому серверу по ssh, добавив при подключении опцию <strong>-A</strong>, то есть вот так:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="powershell">ssh -A user@host:port</pre>
<p>Или можно установить опцию ForwardAgent yes в файле <strong>~/.ssh/config</strong> на вашем локальном компьютере:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="raw">Host *
  ForwardAgent yes</pre>
<p>Для проверки, что всё хорошо и ключ проброшен, на удалённом сервере можно выполнить команду:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="powershell">echo "$SSH_AUTH_SOCK"</pre>
<p>Если всё ок, то она должна вернуть что-то похожее на:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="powershell">/tmp/ssh-DCIux21917/agent.21917</pre>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/linux/ssh-probros-privatnogo-kljucha/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>