<?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>Linux &#8211; REMADMIN</title>
	<atom:link href="https://remadmin.com/blog/linux/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.4</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>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>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>Инкрементальный бэкап через 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>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>
		<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>