"Быстрый
<br />
Вот что ломается на домашнем сервере в первый год работы — по частоте и болезненности:</p>
<ul>
<li><strong>Docker съел диск</strong> — логи контейнеров без ротации и мусорные образы заполняют раздел до 100%</li>
<li><strong>SSD изнашивается быстрее ожидаемого</strong> — journald и Docker пишут непрерывно, TBW улетает незаметно</li>
<li><a title="Резервное копирование MikroTik RouterOS 7 в Telegram: рабочий скрипт и разбор ошибок" href="https://it-apteka.com/rezervnoe-kopirovanie-mikrotik-routeros-7-v-telegram-avtomatizacija-za-20-minut/" target="_blank" rel="noopener" data-wpil-monitor-id="3004">Резервные копии не работали — скрипт</a> был, проверки не было, и в нужный момент бэкап оказался пустым</li>
<li><strong>ИБП с аппроксимированной синусоидой убил блок питания</strong> — ATX БП требует чистую синусоиду</li>
<li><strong>Контейнеры после обновления перестали стартовать</strong> — автообновление без проверки совместимости</li>
</ul>
<p>
<h2>Год назад всё было нормально</h2>
<p>Поднял домашний сервер. Поставил Proxmox, развернул пяток <a class="wpil_keyword_link" href="https://it-apteka.com/tag/docker/" target="_blank" rel="noopener" title="Docker" data-wpil-keyword-link="linked" data-wpil-monitor-id="3012">Docker</a>-контейнеров, настроил Wireguard, добавил Nextcloud и медиасервер. Всё работает. Написал в заметки «сервер готов» и забыл.</p>
<p>Через год — три инцидента за две недели. <a class="wpil_keyword_link" href="https://it-apteka.com/tag/ssd/" target="_blank" rel="noopener" title="ssd" data-wpil-keyword-link="linked" data-wpil-monitor-id="3006">SSD</a> с неожиданным износом, диск под Docker забит в ноль, резервная копия которая не делалась три месяца, и блок питания который не пережил первого серьезного отключения света. Всё это классика, которую каждый проходит сам — потому что никто об этом не предупреждает заранее.</p>
<p>Эта статья — разбор того, что идёт не так на домашнем сервере через 8-12 месяцев непрерывной работы. С командами диагностики, конкретными цифрами и тем, как это исправить до того, как данные исчезнут.</p>
<p>Речь идёт о типовом стеке: Linux (Debian/Ubuntu или Proxmox), Docker Compose, несколько постоянных контейнеров, <a title="Какой SSD диск лучше купить в 2026 году: Мнение эксперта" href="https://it-apteka.com/kakoj-ssd-disk-luchshe-kupit-v-2026-godu-mnenie-jeksperta/" target="_blank" rel="noopener" data-wpil-monitor-id="3002">SSD как системный диск</a>, работа 24/7.</p>
<h2>Проблема первая: SSD износ</h2>
<h3>Почему домашний сервер убивает SSD быстро</h3>
<p>В домашнем ПК SSD живёт годами — потому что пишет мало. Сервер работает иначе. Он пишет постоянно: journald собирает логи ядра и сервисов, <a title="Автоматическое обновление Docker контейнеров: полное руководство и примеры" href="https://it-apteka.com/avtomaticheskoe-obnovlenie-docker-kontejnerov-polnoe-rukovodstvo-i-primery/" target="_blank" rel="noopener" data-wpil-monitor-id="2995">Docker пишет stdout всех контейнеров</a> в JSON-файлы, Nextcloud индексирует файлы, базы данных фиксируют транзакции. Это всё непрерывная запись 24/7.</p>
<p>Потребительский SSD на 500 ГБ обычно имеет TBW (Total Bytes Written) в диапазоне 150-300 ТБ. Звучит много. Но при записи 10-20 ГБ в сутки — а это скромная нагрузка для сервера с Docker — ресурс заканчивается через 4-8 лет. Если логирование не ограничено, цифра реально хуже.</p>
<p>Проверь текущий износ. Для SATA SSD:</p>
<pre><code class="language-bash">
sudo apt install smartmontools -y
sudo smartctl -A /dev/sda | grep -E "Wear|Life|Written|TBW"
</code></pre>
<p>Для <a class="wpil_keyword_link" href="https://it-apteka.com/tag/nvme/" target="_blank" rel="noopener" title="nvme" data-wpil-keyword-link="linked" data-wpil-monitor-id="3013">NVMe</a>:</p>
<pre><code class="language-bash">
sudo smartctl -a /dev/nvme0 | grep -E "Percentage Used|Data Units Written"
</code></pre>
<p>Что смотреть в выводе:</p>
<table>
<thead>
<tr>
<th>Атрибут SMART</th>
<th>Значение</th>
<th>Что означает</th>
</tr>
</thead>
<tbody>
<tr>
<td>SSD_Life_Left (ID 231)</td>
<td>97 = 3% износа, 70 = 30% износа</td>
<td>Прямой остаток ресурса у Kingston</td>
</tr>
<tr>
<td>Wear_Leveling_Count (ID 177)</td>
<td>Чем ниже значение — тем выше износ</td>
<td>Samsung, другие производители</td>
</tr>
<tr>
<td>Media_Wearout_Indicator (ID 233)</td>
<td>Близко к 0 — диск при смерти</td>
<td>Intel SSD</td>
</tr>
<tr>
<td>Percentage Used (NVMe)</td>
<td>0-100%, 100% = исчерпан ресурс</td>
<td>Все NVMe диски</td>
</tr>
<tr>
<td>Power_On_Hours (ID 9)</td>
<td>8760 часов = 1 год работы</td>
<td>Проверка фактического uptime</td>
</tr>
</tbody>
</table>
<p>Если SSD_Life_Left уже ниже 80% после первого года — у тебя проблема с интенсивностью записи. Надо искать источник. При этом не паникуй — диск не упадёт завтра. У тебя есть время найти виновника, ограничить запись и планово заменить диск прежде чем он начнёт показывать ошибки. Именно для этого нужен регулярный SMART-мониторинг — чтобы менять диск в рабочее время по своему расписанию, а не в ту ночь когда он решит умереть.</p>
<h3>Найди что убивает диск</h3>
<p>Установи iotop и посмотри кто пишет прямо сейчас:</p>
<pre><code class="language-bash">
sudo apt install iotop -y
sudo iotop -o -d 5
</code></pre>
<p>Флаг <code>-o</code> показывает только процессы с активным I/O. Флаг <code>-d 5</code> обновляет каждые 5 секунд. Смотри колонку DISK WRITE.</p>
<p>Чаще всего виновники три: journald, Docker, и база данных (если Nextcloud или что-то похожее работает без кэша).</p>
<h3>Ограничь journald</h3>
<p>По умолчанию journald хранит до 10% дискового пространства. На 500 ГБ диске — это 50 ГБ логов. И пишет их непрерывно без ограничений по скорости.</p>
<pre><code class="language-bash">
sudo nano /etc/systemd/journald.conf
</code></pre>
<p>Добавь или раскомментируй:</p>
<pre><code class="language-text">
[Journal]
SystemMaxUse=500M
SystemMaxFileSize=50M
MaxRetentionSec=1month
RateLimitInterval=30s
RateLimitBurst=10000
</code></pre>
<p>Применить без перезагрузки:</p>
<pre><code class="language-bash">
sudo systemctl restart systemd-journald
sudo journalctl --vacuum-size=500M
</code></pre>
<p>После этого journald будет хранить не больше 500 МБ логов и автоматически ротировать старые записи.</p>
<h3>Ограничь логи Docker</h3>
<p>Docker по умолчанию пишет stdout каждого контейнера в JSON без каких-либо лимитов. Активный контейнер типа Nextcloud или nginx с access-логами может накопить десятки гигабайт за несколько месяцев.</p>
<p>Глобальный лимит через /etc/docker/daemon.json:</p>
<pre><code class="language-bash">
sudo nano /etc/docker/daemon.json
</code></pre>
<pre><code class="language-text">
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}
</code></pre>
<pre><code class="language-bash">
sudo systemctl restart docker
</code></pre>
<p>Это применяется к новым контейнерам. Уже запущенные контейнеры нужно пересоздать. Для каждого сервиса в docker-compose.yml можно прописать индивидуально:</p>
<pre><code class="language-text">
services:
nextcloud:
image: nextcloud:latest
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
</code></pre>
<p>После этих двух изменений суточная запись на диск падает в 3-5 раз на типовом homelab-стеке. Разница заметна не только по SMART-данным — сервер начинает быстрее отвечать на запросы в периоды активной работы, потому что диск не занят непрерывной записью фоновых логов.</p>
<h2>Проблема вторая: Docker съел весь диск</h2>
<h3>Откуда берутся гигабайты</h3>
<p>Через год работы /var/lib/docker превращается в свалку. Остановленные контейнеры, старые образы от обновлений, анонимные тома которые никто не удалял, build cache если ты собирал образы локально. Это всё занимает место молча.</p>
<p>Проверь текущее состояние:</p>
<pre><code class="language-bash">
docker system df
</code></pre>
<p>Вывод покажет четыре строки: Images, Containers, Volumes, Build Cache — с размером и тем, сколько можно освободить.</p>
<p>Если нужно понять что именно занимает место в /var/lib/docker:</p>
<pre><code class="language-bash">
sudo du -sh /var/lib/docker/* | sort -rh | head -10
</code></pre>
<p>Самые частые виновники: overlay2 (слои образов и файловые системы контейнеров), containers (JSON-логи), volumes (данные без привязки к сервису).</p>
<h3>Очистка по уровням безопасности</h3>
<p>Три уровня агрессивности — от безопасного к радикальному.</p>
<p><strong>Уровень 1 — безопасно, ничего живого не трогает:</strong></p>
<pre><code class="language-bash">
docker container prune -f
docker image prune -f
docker network prune -f
</code></pre>
<p>Удаляет остановленные контейнеры, образы без тегов (dangling), неиспользуемые <a class="wpil_keyword_link" href="https://it-apteka.com/category/networks/" target="_blank" rel="noopener" title="Сети" data-wpil-keyword-link="linked" data-wpil-monitor-id="3008">сети</a>. Volumes не трогает.</p>
<p><strong>Уровень 2 — освобождает больше, но осторожно:</strong></p>
<pre><code class="language-bash">
docker system prune -f
</code></pre>
<p>То же самое одной командой плюс build cache. Запущенные контейнеры и их образы не трогает.</p>
<p><strong>Уровень 3 — ядерный вариант, только если точно знаешь что делаешь:</strong></p>
<pre><code class="language-bash">
docker system prune -a -f --volumes
</code></pre>
<p>Удаляет всё включая образы, которые не используются прямо сейчас. После этого все остановленные сервисы придётся пересоздавать с нуля — образы скачаются заново, но <a title="Docker volumes: как хранить данные контейнеров и не потерять их после prune" href="https://it-apteka.com/docker-volumes-kak-hranit-dannye-kontejnerov-i-ne-poterjat-ih-posle-prune/" target="_blank" rel="noopener" data-wpil-monitor-id="3003">данные в named volumes</a> сохранятся если ты их не передал в —volumes.</p>
"Важно
<br />
Если /var/lib/docker/containers занимает много места — это JSON-логи контейнеров. Их можно обнулить без остановки контейнера, но данные за прошлое исчезнут навсегда. После этого обязательно настрой лимиты логов через daemon.json.<br />
<pre><code class="language-bash">
# Посмотреть размер логов по контейнерам
sudo du -sh /var/lib/docker/containers/*/*-json.log | sort -rh
# Обнулить все логи (осторожно - потеря истории)
sudo sh -c "truncate -s 0 /var/lib/docker/containers/*/*-json.log"
</code></pre>
<h3>Автоматическая уборка через cron</h3>
<p>Вместо того чтобы чистить вручную раз в полгода — поставь автоуборку раз в неделю. Только безопасный уровень, запущенного не тронет:</p>
<pre><code class="language-bash">
sudo crontab -e
</code></pre>
<pre><code class="language-text">
# Docker cleanup каждое воскресенье в 3:00
0 3 * * 0 /usr/bin/docker system prune -f >> /var/log/docker-prune.log 2>&1
</code></pre>
<h2>Проблема третья: резервные копии которых нет</h2>
<h3>Иллюзия бэкапа</h3>
<p>У большинства домашних серверов через год ситуация такая: <a class="wpil_keyword_link" href="https://it-apteka.com/category/scripts/" target="_blank" rel="noopener" title="Скрипты" data-wpil-keyword-link="linked" data-wpil-monitor-id="3015">скрипт</a> написан, cron стоит, но никто ни разу не проверил что внутри архива. А внутри — ошибка монтирования с первого месяца, которую скрипт проглотил и продолжил работу. Технически бэкап «делается» каждую ночь. Фактически он пустой.</p>
<p>Второй сценарий: бэкап делается на тот же диск. Диск умирает — и основные данные, и бэкап уходят вместе. Это не бэкап, это иллюзия безопасности.</p>
<p>Правило 3-2-1 коротко: три копии данных, на двух разных носителях, одна из которых — вне сервера (облако, внешний диск у родственников, VPS).</p>
<h3>Restic — минимальный рабочий бэкап</h3>
<p>Restic — утилита для инкрементных зашифрованных бэкапов. Один бинарник, работает с локальными директориями, SFTP, S3-совместимым хранилищем. Устанавливается за 30 секунд.</p>
<p>Почему именно restic, а не rsync или простой tar? Restic делает дедупликацию на уровне блоков — если один и тот же файл не менялся, он не копируется заново. Это экономит место и время. Restic шифрует всё на стороне клиента до передачи — даже если бэкап хранится на сервере которому ты не полностью доверяешь, данные защищены. Restic поддерживает ротацию снапшотов с гибкими правилами — держи 7 ежедневных, 4 еженедельных, 6 месячных, остальное автоматически удаляется. И главное — restic позволяет проверить целостность репозитория одной командой и восстановить конкретный файл или папку без разворачивания всего архива.</p>
<pre><code class="language-bash">
sudo apt install restic -y
restic version
</code></pre>
<p>Инициализируй репозиторий (например, на внешнем диске или в /mnt/backup):</p>
<pre><code class="language-bash">
export RESTIC_PASSWORD="твой-пароль-запомни-его"
restic init --repo /mnt/backup/homelab
</code></pre>
<p>Первый бэкап важных данных:</p>
<pre><code class="language-bash">
restic backup \
/home \
/etc \
/opt/docker-compose \
--repo /mnt/backup/homelab \
--exclude="/home/*/.cache" \
--exclude="/home/*/.local/share/Trash"
</code></pre>
<p>Проверь что бэкап реально содержит файлы:</p>
<pre><code class="language-bash">
restic snapshots --repo /mnt/backup/homelab
restic ls latest --repo /mnt/backup/homelab | head -30
</code></pre>
<p>Это критически важный шаг. Команда <code>restic ls</code> показывает содержимое последнего снапшота. Если там файлы — бэкап работает.</p>
<h3>Что бэкапить на Docker-сервере</h3>
<table>
<thead>
<tr>
<th>Что</th>
<th>Путь</th>
<th>Почему важно</th>
</tr>
</thead>
<tbody>
<tr>
<td>Docker Compose файлы</td>
<td>/opt/stacks/ или /home/user/docker/</td>
<td>Вся конфигурация сервисов</td>
</tr>
<tr>
<td>Named volumes</td>
<td>/var/lib/docker/volumes/</td>
<td>Данные баз данных, Nextcloud, и т.д.</td>
</tr>
<tr>
<td>Конфиги системы</td>
<td>/etc/</td>
<td>Сеть, firewall, cron, systemd юниты</td>
</tr>
<tr>
<td>Домашние директории</td>
<td>/home/</td>
<td>SSH ключи, .env файлы, скрипты</td>
</tr>
<tr>
<td>Certs и secrets</td>
<td>/etc/letsencrypt/, /opt/secrets/</td>
<td>Без них сервисы не поднимутся</td>
</tr>
</tbody>
</table>
"Про
<br />
Перед бэкапом volumes желательно остановить контейнеры которые активно пишут в базу данных. Иначе рискуешь получить инконсистентный снапшот. Для Nextcloud и PostgreSQL это критично.<br />
<h3>Автоматизация и ротация</h3>
<pre><code class="language-bash">
sudo nano /opt/scripts/backup.sh
</code></pre>
<pre><code class="language-bash">
#!/bin/bash
export RESTIC_REPOSITORY="/mnt/backup/homelab"
export RESTIC_PASSWORD="твой-пароль"
LOG="/var/log/restic-backup.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$DATE] Starting backup..." >> $LOG
# Остановить контейнеры с БД перед бэкапом
docker stop nextcloud_db 2>/dev/null
# Сам бэкап
restic backup \
/home \
/etc \
/opt \
/var/lib/docker/volumes \
--exclude="/var/lib/docker/volumes/*/_data/cache" \
>> $LOG 2>&1
RESULT=$?
# Запустить обратно
docker start nextcloud_db 2>/dev/null
# Удалить старые снапшоты: держать 7 дней, 4 недели, 6 месяцев
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--prune \
>> $LOG 2>&1
if [ $RESULT -eq 0 ]; then
echo "[$DATE] Backup completed successfully" >> $LOG
else
echo "[$DATE] BACKUP FAILED with code $RESULT" >> $LOG
fi
</code></pre>
<pre><code class="language-bash">
chmod +x /opt/scripts/backup.sh
# Добавить в cron
sudo crontab -e
</code></pre>
<pre><code class="language-text">
# Бэкап каждую ночь в 2:00
0 2 * * * /opt/scripts/backup.sh
</code></pre>
<h2>Проблема четвёртая: ИБП с неправильной синусоидой</h2>
<h3>Почему дешевый ИБП хуже чем его отсутствие</h3>
<p>ИБП покупают чтобы защитить сервер от отключения света. Логично. Но большинство бюджетных ИБП (до 5000-6000 рублей) выдают при переходе на батарею не чистую синусоиду, а ступенчатую аппроксимацию. Выглядит похоже, но для современных блоков питания с активным PFC это проблема.</p>
<p>Блоки питания с активным PFC (это 90% современных ATX блоков) не рассчитаны на ступенчатую аппроксимацию. В лучшем случае БП начинает перегреваться и срабатывает защита — сервер выключается именно тогда когда свет пропал. В худшем — БП выходит из строя через несколько месяцев такой работы.</p>
<p>Проверь свой ИБП: в характеристиках должно быть написано «чистая синусоида» или «Pure Sine Wave». Если написано «аппроксимированная», «ступенчатая» или просто ничего — это не подходит для сервера с нормальным блоком питания.</p>
<table>
<thead>
<tr>
<th>Тип выходного сигнала</th>
<th>Подходит для сервера</th>
<th>Примерная цена</th>
</tr>
</thead>
<tbody>
<tr>
<td>Чистая синусоида (Pure Sine Wave)</td>
<td>Да, без ограничений</td>
<td>от 8 000 руб.</td>
</tr>
<tr>
<td>Ступенчатая аппроксимация</td>
<td>Нет (для БП с активным PFC)</td>
<td>2 000 — 5 000 руб.</td>
</tr>
<tr>
<td>Офлайн ИБП без AVR</td>
<td>Нет (просадки напряжения)</td>
<td>до 3 000 руб.</td>
</tr>
</tbody>
</table>
<p>Для домашнего сервера минимум — линейно-интерактивный ИБП с чистой синусоидой и USB-портом для корректного завершения работы при разряде батареи. APC Back-UPS Pro и CyberPower CP1500PFCLCD из проверенных вариантов.</p>
<h3>Настрой автовыключение через apcupsd или NUT</h3>
<p>ИБП без интеграции с системой — это просто дорогой удлинитель с батарейкой. Смысл в том, чтобы при разряде батареи сервер завершил работу корректно.</p>
<p>Для APC через USB:</p>
<pre><code class="language-bash">
sudo apt install apcupsd -y
sudo nano /etc/apcupsd/apcupsd.conf
</code></pre>
<pre><code class="language-text">
UPSNAME homeserver
UPSCABLE usb
UPSTYPE usb
DEVICE
MINUTES 5
TIMEOUT 0
BATTERYLEVEL 30
</code></pre>
<pre><code class="language-bash">
sudo systemctl enable apcupsd
sudo systemctl start apcupsd
apcaccess status
</code></pre>
<p>Параметр MINUTES 5 означает: начать корректное завершение работы если батарея держит меньше 5 минут. BATTERYLEVEL 30 — завершить работу если заряд упал ниже 30%. Оба условия проверяются по OR — что наступит раньше.</p>
<p>Для других ИБП (Ippon, Eaton, Powerware) — смотри Network UPS Tools (NUT), он поддерживает практически всё что подключается по USB или SNMP.</p>
<h2>Проблема пятая: контейнеры сломались после обновления</h2>
<h3>Автообновление без головы</h3>
<p>Watchtower, Diun, ручной <code>docker pull</code> по расписанию — все эти инструменты делают одно: обновляют образы контейнеров. Проблема в том, что новая версия образа не всегда совместима с текущими данными или конфигурацией. Особенно это касается баз данных и Nextcloud.</p>
<p>PostgreSQL 15 не читает данные PostgreSQL 14 без явной миграции. Nextcloud требует последовательных обновлений версий. Если автообновление прыгнуло через мажорную версию — контейнер не стартует, а данные целы, но недоступны.</p>
<p>Алгоритм диагностики когда контейнер не поднимается:</p>
<pre><code class="language-bash">
# Посмотреть статус всех контейнеров
docker ps -a
# Посмотреть лог проблемного контейнера
docker logs имя_контейнера --tail 50
# Посмотреть события Docker
docker events --since 1h --until now
</code></pre>
<p>Типичные ошибки после обновления и что с ними делать:</p>
<table>
<thead>
<tr>
<th>Сообщение в логах</th>
<th>Причина</th>
<th>Решение</th>
</tr>
</thead>
<tbody>
<tr>
<td>database files are incompatible with server</td>
<td>Мажорное обновление PostgreSQL</td>
<td>Откатить образ, сделать pg_dump, мигрировать</td>
</tr>
<tr>
<td>Nextcloud is in maintenance mode</td>
<td>Обновление не завершило миграцию</td>
<td>docker exec -u www-data nextcloud php occ upgrade</td>
</tr>
<tr>
<td>exec /entrypoint.sh: exec format error</td>
<td>Образ собран под другую архитектуру</td>
<td>Указать явный тег платформы в compose</td>
</tr>
<tr>
<td>Permission denied на volume</td>
<td>Новая версия сменила UID пользователя</td>
<td>chown -R новый_uid:gid /путь/к/volume</td>
</tr>
</tbody>
</table>
<h3>Как обновлять без риска</h3>
<p>Рабочая схема для homelab: перед обновлением бэкап volume, потом обновление, потом проверка. Автоматически это не сделать безопасно — только вручную или с паузой на проверку.</p>
<p><a title="Обновление Docker-контейнеров автоматически: Watchtower vs Diun" href="https://it-apteka.com/obnovlenie-docker-kontejnerov-avtomaticheski-watchtower-vs-diun/" target="_blank" rel="noopener" data-wpil-monitor-id="2997">Watchtower в режиме автоматического</a> обновления образов — это удобно для сервисов без состояния. Для сервисов с базами данных, файловыми хранилищами или сложной конфигурацией — только уведомления о новых версиях, а само обновление руками. Diun (Docker Image Update Notifier) хорошо справляется с задачей мониторинга: присылает уведомления когда появляется новый образ, не трогая сами контейнеры.</p>
<p>Для Nextcloud обновление требует отдельного внимания. Нельзя прыгать через мажорные версии — только последовательно. С 25 на 27 напрямую не пройдёт, нужно через 26. Это особенность Nextcloud, и она болезненно проявляется когда Watchtower автоматически обновил образ через несколько версий за период пока ты не смотрел на сервер.</p>
<pre><code class="language-bash">
# Зафиксировать текущую версию образа
docker inspect имя_контейнера | grep Image
# Сделать бэкап volume перед обновлением
docker run --rm \
-v имя_volume:/data \
-v /tmp/backup:/backup \
alpine tar czf /backup/volume-backup-$(date +%Y%m%d).tar.gz /data
# Обновить образ
docker pull образ:тег
docker compose up -d имя_сервиса
# Проверить что сервис живой
docker logs имя_контейнера --tail 20
curl -s http://localhost:порт/health
</code></pre>
<p>Если что-то пошло не так — откат через pinning версии в docker-compose.yml:</p>
<pre><code class="language-text">
services:
postgres:
image: postgres:15.6 # явный тег, не :latest
</code></pre>
<p>Конкретный тег вместо :latest — это единственный способ контролировать что именно запускается. С :latest ты никогда не знаешь что задеплоилось после <code>docker pull</code>.</p>
<h2>Архитектура хранения: правильно с самого начала</h2>
<h3>Одному диску доверять нельзя</h3>
<p>Типичная схема homelab: один SSD, на нём система, Docker, данные и бэкапы. Это не схема, это бомба замедленного действия. Когда диск умирает — умирает всё сразу: ОС, контейнеры, данные, и все бэкапы которые ты туда писал.</p>
<p>Минимальная разумная схема для домашнего сервера выглядит иначе:</p>
<pre class="mermaid">%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'lineColor': '#64748b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
A["Домашний сервер"] --> B["SSD - система и Docker"]
A --> C["HDD - данные и медиа"]
B --> D["Внешний HDD - локальный бэкап"]
C --> D
D --> E["Облако S3 / VPS - оффсайт бэкап"]
style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style E fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style D fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#c2410c
</pre>
<p>SSD отвечает за скорость: система, /var/lib/docker, базы данных. <a class="wpil_keyword_link" href="https://it-apteka.com/tag/hdd/" target="_blank" rel="noopener" title="hdd" data-wpil-keyword-link="linked" data-wpil-monitor-id="3009">HDD</a> отвечает за объём: медиафайлы, архивы, бэкапы. Разделение ролей дисков снижает нагрузку на SSD и даёт понятную схему резервирования.</p>
<h3>Разбей разделы правильно на старте</h3>
<p>Если ты только разворачиваешь сервер — сделай это правильно сразу. Потом переразбивать без полного переустановки сложно.</p>
<table>
<thead>
<tr>
<th>Раздел</th>
<th>Размер</th>
<th>Назначение</th>
</tr>
</thead>
<tbody>
<tr>
<td>/</td>
<td>50-80 ГБ</td>
<td>ОС, системные пакеты</td>
</tr>
<tr>
<td>/var</td>
<td>100-200 ГБ</td>
<td>Docker, логи, journald</td>
</tr>
<tr>
<td>/home</td>
<td>остаток SSD</td>
<td>Конфиги, скрипты, compose-файлы</td>
</tr>
<tr>
<td>/mnt/data</td>
<td>весь HDD</td>
<td>Медиа, файлы, бэкапы</td>
</tr>
</tbody>
</table>
<p>Отдельный раздел под /var важен потому что именно туда пишут Docker и journald. Если /var переполнится — система остаётся работоспособной, только Docker начнёт ругаться. Если переполняется / — всё гораздо хуже, вплоть до паники ядра.</p>
<p>Смонтировать /var/lib/docker на отдельный диск можно и после установки через symlink или mount —bind, но лучше предусмотреть это заранее.</p>
<pre><code class="language-bash">
# Проверить текущую разбивку и использование
lsblk
df -h
# Посмотреть что пишет на диск прямо сейчас
sudo iotop -o -d 3
</code></pre>
<h2>Безопасность за год незаметно деградирует</h2>
<h3>Что происходит пока сервер просто работает</h3>
<p>Поднял, настроил, забыл. Через год выясняется: SSH всё ещё принимает подключения по паролю, fail2ban стоит но правило для порта Nextcloud так и не добавил, UFW открыт для всей подсети потому что «так было удобнее». Это не катастрофа, но это постепенное снижение планки безопасности которое незаметно накапливается.</p>
<p>Быстрая проверка текущего состояния:</p>
<pre><code class="language-bash">
# SSH - что разрешено
sudo grep -E "PasswordAuthentication|PermitRootLogin|Port" /etc/ssh/sshd_config
# UFW - что открыто
sudo ufw status verbose
# Fail2ban - активные правила и статистика
sudo fail2ban-client status
sudo fail2ban-client status sshd
# Последние неудачные попытки входа
sudo lastb | head -20
# Активные сессии
who -a
</code></pre>
<p>Что должно быть в /etc/ssh/sshd_config на сервере который торчит в интернет или в локальную сеть с Wi-Fi гостями:</p>
<pre><code class="language-text">
PasswordAuthentication no
PermitRootLogin no
Port 22222
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers твой_пользователь
</code></pre>
<pre><code class="language-bash">
sudo systemctl restart sshd
</code></pre>
"После
<br />
Если меняешь порт SSH — сначала открой новый порт в UFW, потом перезапусти sshd, и только потом закрой старый. Иначе заблокируешь себя. Проверяй через второй терминал.<br />
<h3>UFW — минимальный firewall без паранойи</h3>
<pre><code class="language-bash">
# Базовая настройка
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Разрешить только нужное
sudo ufw allow 22222/tcp # SSH на нестандартном порту
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 51820/udp # WireGuard если используешь
# Включить
sudo ufw enable
sudo ufw status numbered
</code></pre>
<p>Для сервисов которые должны быть доступны только из локальной сети — ограничь по подсети:</p>
<pre><code class="language-bash">
# Nextcloud только из локалки
sudo ufw allow from 192.168.1.0/24 to any port 8080
# Portainer только с твоего IP
sudo ufw allow from 192.168.1.10 to any port 9443
</code></pre>
<h3>Автоматические обновления безопасности</h3>
<p>Обновлять вручную раз в месяц — хорошая привычка. Но патчи безопасности выходят непрерывно, и между твоими обновлениями может оказаться критическая уязвимость. Для homelab разумный компромисс — автоматические обновления только security-пакетов, всё остальное вручную.</p>
<pre><code class="language-bash">
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
</code></pre>
<p>Это включает автоматическую установку пакетов из репозитория security.ubuntu.com или debian-security. Мажорные обновления и Docker образы — по-прежнему вручную.</p>
<h2>Сетевая часть: что проседает незаметно</h2>
<h3>DNS-резолвер на домашнем сервере</h3>
<p>Многие ставят AdGuard Home или Pi-hole на домашний сервер в первый же день. Потом через полгода обнаруживают: <a class="wpil_keyword_link" href="https://it-apteka.com/tag/dns/" target="_blank" rel="noopener" title="DNS" data-wpil-keyword-link="linked" data-wpil-monitor-id="3011">DNS</a> перестал отвечать — и не работает вообще ничего в домашней сети, потому что все устройства смотрят на этот сервер. А причина — переполнен диск, контейнер упал, и никто не заметил пока кто-то из домашних не начал жаловаться что интернет «не работает».</p>
<p>Для DNS-сервисов которые влияют на всю <a title="SNMP: что это такое, как работает и как настроить мониторинг сети" href="https://it-apteka.com/snmp-chto-jeto-takoe-kak-rabotaet-i-kak-nastroit-monitoring-seti/" target="_blank" rel="noopener" data-wpil-monitor-id="2998">сеть — мониторинг</a> обязателен. Минимум: проверка доступности раз в минуту через cron или healthcheck в docker-compose.</p>
<pre><code class="language-text">
services:
adguardhome:
image: adguard/adguardhome:latest
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000"]
interval: 60s
timeout: 10s
retries: 3
start_period: 30s
restart: unless-stopped
</code></pre>
<p>Параметр <code>restart: unless-stopped</code> вместо <code>always</code> — важное отличие. При <code>always</code> контейнер будет перезапускаться даже если ты его остановил вручную для обслуживания. При <code>unless-stopped</code> — перезапускается после краша и при старте системы, но не если остановлен явно.</p>
<h3>WireGuard: проверка туннеля</h3>
<p>WireGuard тихо разрывает соединение если нет трафика и не настроен keepalive. Через несколько часов без активности туннель «замирает» — пинг идёт, но первые пакеты теряются пока не произойдёт повторное согласование ключей. На практике это выглядит как «VPN иногда не работает» без явной причины.</p>
<pre><code class="language-bash">
# Проверить состояние всех туннелей
sudo wg show
# Проверить последний handshake (в секундах от текущего времени)
sudo wg show wg0 latest-handshakes
</code></pre>
<p>Если latest-handshake больше 3-5 минут при активном использовании — туннель завис. В конфиге peer добавь:</p>
<pre><code class="language-text">
[Peer]
PersistentKeepalive = 25
</code></pre>
<p>Это отправляет keepalive каждые 25 секунд и не даёт туннелю замерзать. Особенно важно если WireGuard используется для удалённого доступа к homelab — без этого соединение будет пропадать после нескольких минут неактивности.</p>
<h2>Обновление операционной системы: когда и как</h2>
<h3>Мажорные обновления дистрибутива — отдельная история</h3>
<p>Ubuntu 22.04 -> 24.04, <a class="wpil_keyword_link" href="https://it-apteka.com/tag/debian/" target="_blank" rel="noopener" title="Debian" data-wpil-keyword-link="linked" data-wpil-monitor-id="3010">Debian</a> 11 -> 12 — это не просто <code>apt upgrade</code>. Это смена версий Python, systemd, ядра, и десятков других компонентов которые могут поломать что-то в твоих контейнерах или системных скриптах. На продакшн-системах такие обновления делают с бэкапом, тестом в staging и окном для отката.</p>
<p>На домашнем <a title="Linux SIP прокси сервер: полное руководство по Kamailio, OpenSIPS и Asterisk" href="https://it-apteka.com/linux-sip-proksi-server-polnoe-rukovodstvo-po-kamailio-opensips-i-asterisk/" target="_blank" rel="noopener" data-wpil-monitor-id="2999">сервере — минимум с полным</a> бэкапом системы перед обновлением. Лучший момент для мажорного обновления дистрибутива: когда у тебя есть час-два на проверку что всё работает после, и когда последний бэкап сделан вчера а не три месяца назад.</p>
<pre><code class="language-bash">
# Перед мажорным обновлением - сделать полный бэкап
restic backup / --exclude=/proc --exclude=/sys --exclude=/dev \
--exclude=/tmp --exclude=/run \
--repo /mnt/backup/homelab
# Проверить что бэкап успешен
restic snapshots --repo /mnt/backup/homelab | tail -3
# Только потом - обновление дистрибутива
sudo do-release-upgrade
</code></pre>
<h3>Proxmox: обновления с подпиской и без</h3>
<p>Если используешь <a class="wpil_keyword_link" href="https://it-apteka.com/category/virtualise/" target="_blank" rel="noopener" title="Виртуализация" data-wpil-keyword-link="linked" data-wpil-monitor-id="3016">Proxmox</a> без enterprise подписки — репозиторий по умолчанию будет пытаться обращаться к pve-enterprise.proxmox.com и ругаться на отсутствие авторизации. Это не мешает работе, но надоедает. Переключись на no-subscription репозиторий:</p>
<pre><code class="language-bash">
# Отключить enterprise репозиторий
sudo sed -i 's/^deb/#deb/g' /etc/apt/sources.list.d/pve-enterprise.list
# Добавить no-subscription
echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
| sudo tee /etc/apt/sources.list.d/pve-no-subscription.list
sudo apt update && sudo apt upgrade -y
</code></pre>
<p>No-subscription репозиторий содержит те же пакеты что и enterprise, просто с небольшой задержкой. Для домашнего сервера это абсолютно нормально.</p>
<h2>Питание и тепловой режим: что убивает железо медленно</h2>
<h3>Перегрев — молчаливый убийца</h3>
<p>Домашний сервер часто стоит в шкафу, под столом или в кладовке. Летом температура окружающего воздуха поднимается на 5-10 градусов. Вентиляция которой хватало зимой — уже не справляется. Процессор начинает троттлить, диски греются, и система работает медленнее без видимой причины.</p>
<p>Проверь текущие температуры:</p>
<pre><code class="language-bash">
sudo apt install lm-sensors smartmontools -y
sudo sensors-detect --auto
sensors
sudo smartctl -A /dev/sda | grep -i temp
sudo smartctl -a /dev/nvme0 | grep -i temp
</code></pre>
<p>Нормальные рабочие температуры для домашнего сервера в простое:</p>
<table>
<thead>
<tr>
<th>Компонент</th>
<th>Норма</th>
<th>Повод проверить</th>
<th>Критично</th>
</tr>
</thead>
<tbody>
<tr>
<td>Процессор (idle)</td>
<td>30-50°C</td>
<td>выше 70°C</td>
<td>выше 90°C</td>
</tr>
<tr>
<td>SSD SATA</td>
<td>25-45°C</td>
<td>выше 60°C</td>
<td>выше 70°C</td>
</tr>
<tr>
<td>NVMe SSD</td>
<td>35-55°C</td>
<td>выше 70°C</td>
<td>выше 85°C</td>
</tr>
<tr>
<td>HDD</td>
<td>25-40°C</td>
<td>выше 50°C</td>
<td>выше 60°C</td>
</tr>
</tbody>
</table>
<p>Если NVMe диск регулярно работает выше 70°C — это ускоряет износ и провоцирует ошибки чтения. Радиатор для NVMe стоит 200-400 рублей и ставится за пять минут. Одно из лучших вложений для домашнего сервера.</p>
<h3>Энергопотребление и выбор железа</h3>
<p>Домашний сервер работает 24/7. Лишние 10 Вт потребления — это 87,6 кВт*ч в год, около 700-900 рублей при текущих тарифах. Разница между Intel N100 (6-10 Вт) и старым Core i7 (65+ Вт) в режиме простоя — это разница в 4000-5000 рублей за электричество в год. Плюс тепловыделение, плюс шум охлаждения.</p>
<p>Включи агрессивное энергосбережение CPU если сервер не занимается вычислениями постоянно:</p>
<pre><code class="language-bash">
sudo apt install linux-cpupower -y
sudo cpupower frequency-set -g powersave
# Сделать постоянным
echo 'GOVERNOR="powersave"' | sudo tee /etc/default/cpupower
sudo systemctl enable cpupower
</code></pre>
<h2>Контейнеры: что идёт не так через год</h2>
<h3>Orphan volumes накапливаются незаметно</h3>
<p>Каждый раз когда удаляешь контейнер через <code>docker rm</code> или <code>docker compose down</code> без флага <code>-v</code> — named volume остаётся. Если потом пересоздаёшь контейнер с другим именем volume — старый висит как мусор. Через год таких «сирот» может накопиться несколько гигабайт.</p>
<pre><code class="language-bash">
# Найти volumes без привязки к контейнерам
docker volume ls -f dangling=true
# Посмотреть размер через system df
docker system df -v
</code></pre>
<p>Хорошая практика: давай volumes явные имена в docker-compose.yml, тогда легче понять что можно удалить:</p>
<pre><code class="language-text">
volumes:
nextcloud_data:
name: nextcloud_data
nextcloud_db:
name: nextcloud_db
portainer_data:
name: portainer_data
</code></pre>
<p>С явными именами <code>docker volume ls</code> показывает понятный список вместо UUID-хэшей. И сразу видно — вот этот volume нужен, а тот непонятный хэш уже полгода не используется.</p>
<h3>Контейнер зависает и не реагирует на stop</h3>
<p>Редкая, но неприятная ситуация: <code>docker stop контейнер</code> ждёт 10 секунд, посылает SIGKILL, и контейнер всё равно не реагирует. Обычно это означает что процесс завис в uninterruptible sleep — ожидание I/O к диску который перестал отвечать, или ядерный дедлок.</p>
<pre><code class="language-bash">
# Узнать PID процесса на хосте
docker inspect имя_контейнера --format '{{.State.Pid}}'
# Проверить dmesg на ошибки диска
sudo dmesg | tail -50 | grep -iE "error|fault|hung_task"
# Принудительное завершение
docker kill --signal=9 имя_контейнера
</code></pre>
<p>Если контейнер завис из-за проблем с диском — проверь SMART данные немедленно. Зависание I/O часто предшествует полному отказу диска на несколько дней или недель.</p>
<h2>Документируй свою инфраструктуру</h2>
<h3>Через год ты не помнишь зачем это настроил</h3>
<p>Это звучит как совет из учебника для первокурсников, но на практике через год смотришь на свой же crontab и не понимаешь что делает скрипт check2.sh который запускается в 4:17. Или находишь открытый порт в UFW и не помнишь для чего его открывал шесть месяцев назад. Можно было открыть для чего-то временного, закрыть задачу, и забыть закрыть порт.</p>
<p>Минимальная документация которая реально спасает:</p>
<ul>
<li>README.md в папке с compose-файлами — что запущено, зачем, как перезапустить</li>
<li>Комментарии в crontab перед каждой задачей: <code># Проверка износа SSD раз в неделю</code></li>
<li>Changelog в один файл — дата, что изменил, зачем</li>
<li><a class="wpil_keyword_link" href="https://it-apteka.com/tag/git/" target="_blank" rel="noopener" title="Git" data-wpil-keyword-link="linked" data-wpil-monitor-id="3014">Git</a> для конфигов — история изменений и возможность откатить</li>
</ul>
<pre><code class="language-bash">
cd /opt/stacks
git init
git add .
git commit -m "initial: базовый стек после первоначальной настройки"
# После каждого значимого изменения
git add docker-compose.yml
git commit -m "nextcloud: обновил до 28.0.1, добавил redis"
</code></pre>
<p>Не нужен GitHub или GitLab — локальный репозиторий достаточен. Главное что у тебя есть история и возможность сделать <code>git diff HEAD~1</code> когда что-то перестало работать после правки конфига. В три ночи это экономит нервы.</p>
<h2>Безопасность за год незаметно деградирует</h2>
<h3>Что происходит пока сервер просто работает</h3>
<p>Поднял, настроил, забыл. Через год выясняется: SSH всё ещё принимает подключения по паролю, fail2ban стоит но правило для порта Nextcloud так и не добавил, UFW открыт для всей подсети потому что «так было удобнее при настройке». Это не катастрофа сегодня, но это постепенное снижение планки безопасности которое незаметно накапливается.</p>
<p>Быстрая проверка текущего состояния:</p>
<pre><code class="language-bash">
# SSH - что разрешено
sudo grep -E "PasswordAuthentication|PermitRootLogin|Port" /etc/ssh/sshd_config
# UFW - что открыто
sudo ufw status verbose
# Fail2ban - активные правила
sudo fail2ban-client status
# Последние неудачные попытки входа по SSH
sudo lastb | head -20
</code></pre>
<p>Что должно быть в /etc/ssh/sshd_config на сервере который доступен снаружи или из гостевой Wi-Fi сети:</p>
<pre><code class="language-text">
PasswordAuthentication no
PermitRootLogin no
MaxAuthTries 3
AllowUsers твой_пользователь
</code></pre>
<pre><code class="language-bash">
sudo systemctl restart sshd
</code></pre>
"После
<br />
Перед перезапуском sshd с новыми настройками — держи открытую сессию. Проверь новое подключение в отдельном терминале. Только если зашло — закрывай старую сессию. Иначе рискуешь заблокировать себя на своём же сервере.<br />
<h3>UFW — минимальный firewall</h3>
<p>Базовая настройка которая закрывает всё лишнее:</p>
<pre><code class="language-bash">
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 51820/udp # WireGuard
sudo ufw enable
sudo ufw status numbered
</code></pre>
<p>Для сервисов которые должны быть доступны только из локальной сети — ограничь по подсети:</p>
<pre><code class="language-bash">
# Portainer только из локалки
sudo ufw allow from 192.168.1.0/24 to any port 9443
# Prometheus только с конкретного IP
sudo ufw allow from 192.168.1.10 to any port 9090
</code></pre>
<h2>Системные требования</h2>
<table>
<thead>
<tr>
<th>Компонент</th>
<th>Минимум</th>
<th>Рекомендуется</th>
<th>Актуальная версия</th>
</tr>
</thead>
<tbody>
<tr>
<td>ОС</td>
<td>Ubuntu 22.04 LTS / Debian 11</td>
<td>Ubuntu 24.04 LTS / Debian 12</td>
<td>Ubuntu 24.04.2 / Debian 12.6</td>
</tr>
<tr>
<td>Docker Engine</td>
<td>24.x</td>
<td>26.x и выше</td>
<td>27.x</td>
</tr>
<tr>
<td>Docker Compose</td>
<td>v2.15</td>
<td>v2.24+</td>
<td>v2.27+</td>
</tr>
<tr>
<td>smartmontools</td>
<td>7.2</td>
<td>7.4</td>
<td>7.4</td>
</tr>
<tr>
<td>restic</td>
<td>0.14</td>
<td>0.16+</td>
<td>0.17.x</td>
</tr>
<tr>
<td>Proxmox VE</td>
<td>7.x</td>
<td>8.x</td>
<td>8.2</td>
</tr>
</tbody>
</table>
<p>На момент публикации актуальны версии указанные выше. Перед установкой или обновлением проверяй свежие релизы на официальных страницах — особенно для Docker и restic, они обновляются часто.</p>
<h2>Мониторинг: знать до того как сломалось</h2>
<h3>Минимальный стек без оверинжиниринга</h3>
<p>Для homelab не нужен Prometheus + Grafana + Alertmanager. Нужно получить уведомление когда диск заполнен на 85%, контейнер упал, или SSD начал сигнализировать. Это решается тремя утилитами.</p>
<pre class="mermaid">%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'lineColor': '#64748b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
A["Домашний сервер 24/7"] --> B["smartd - мониторинг дисков"]
A --> C["cron + скрипт - диск и контейнеры"]
A --> D["apcupsd - состояние ИБП"]
B --> E["Email / Telegram алерт"]
C --> E
D --> E
E --> F["Ты узнал до падения"]
style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style F fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style E fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#c2410c
</pre>
<p>Smartd уже входит в пакет smartmontools. Настрой его на автоматическую проверку дисков и уведомление при проблемах:</p>
<pre><code class="language-bash">
sudo nano /etc/smartd.conf
</code></pre>
<pre><code class="language-text">
# Мониторинг всех дисков, короткий тест раз в сутки, длинный раз в неделю
DEVICESCAN -a -o on -S on -n standby,q \
-s (S/../.././02|L/../../6/03) \
-W 4,45,50 \
-m root -M exec /usr/share/smartmontools/smartd-runner
</code></pre>
<pre><code class="language-bash">
sudo systemctl enable smartd
sudo systemctl restart smartd
</code></pre>
<p>Простой скрипт мониторинга диска и контейнеров с отправкой в системный лог (или адаптируй под <a class="wpil_keyword_link" href="https://t.me/it_apteka_com/34" target="_blank" rel="noopener" title="Telegram" data-wpil-keyword-link="linked" data-wpil-monitor-id="3005">Telegram</a> webhook):</p>
<pre><code class="language-bash">
#!/bin/bash
# /opt/scripts/check-health.sh
THRESHOLD_DISK=85
# Проверка диска
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$DISK_USAGE" -gt "$THRESHOLD_DISK" ]; then
logger -t homelab-check "WARNING: Root disk usage is ${DISK_USAGE}%"
fi
# Проверка Docker контейнеров
STOPPED=$(docker ps -a --filter "status=exited" --format "{{.Names}}" | tr '\n' ' ')
if [ -n "$STOPPED" ]; then
logger -t homelab-check "WARNING: Stopped containers: $STOPPED"
fi
# Проверка SSD износа (Kingston/Intel)
SSD_LIFE=$(sudo smartctl -A /dev/sda | grep -E "SSD_Life_Left|Media_Wearout" | awk '{print $4}' | head -1)
if [ -n "$SSD_LIFE" ] && [ "$SSD_LIFE" -lt 70 ]; then
logger -t homelab-check "WARNING: SSD life remaining: ${SSD_LIFE}%"
fi
</code></pre>
<pre><code class="language-bash">
chmod +x /opt/scripts/check-health.sh
# Запускать каждый час
echo "0 * * * * /opt/scripts/check-health.sh" | sudo crontab -
</code></pre>
<p>Результаты скрипта видны в <code>sudo journalctl -t homelab-check</code>. Если хочешь Telegram — замени <code>logger</code> на <code>curl</code> с bot API, это 10 минут работы.</p>
<h2>Профилактика: что делать раз в квартал</h2>
<h3>Ежемесячные задачи</h3>
<p>Раз в месяц уходит 15-20 минут. Это минимум который не даёт накапливаться проблемам до критического уровня. Обновление системы и пакетов — первое что стоит делать регулярно. Не мажорные обновления дистрибутива, а просто <code>apt update && apt upgrade</code>. Это закрывает уязвимости в безопасности которые появляются постоянно, и обновляет утилиты вроде smartmontools и restic.</p>
<pre><code class="language-bash">
sudo apt update
sudo apt upgrade -y
sudo apt autoremove -y
</code></pre>
<p>Проверка логов на ошибки — занимает две минуты но может показать проблему которая накапливается незаметно:</p>
<pre><code class="language-bash">
sudo journalctl -p err --since "1 month ago" | tail -50
sudo dmesg | grep -iE "error|fail|bad sector" | tail -20
</code></pre>
<h3>Квартальные задачи</h3>
<p>Один раз потратить час раз в три месяца — это намного лучше чем восстанавливать сервер после инцидента.</p>
<table>
<thead>
<tr>
<th>Задача</th>
<th>Команда</th>
<th>Периодичность</th>
</tr>
</thead>
<tbody>
<tr>
<td>Проверить износ SSD</td>
<td>smartctl -A /dev/sda</td>
<td>Раз в квартал</td>
</tr>
<tr>
<td>Проверить бэкап</td>
<td>restic snapshots && restic check</td>
<td>Раз в месяц</td>
</tr>
<tr>
<td>Протестировать восстановление</td>
<td>restic restore latest —target /tmp/restore-test</td>
<td>Раз в квартал</td>
</tr>
<tr>
<td>Очистить Docker</td>
<td>docker system prune -f</td>
<td>Раз в месяц (или cron)</td>
</tr>
<tr>
<td>Проверить батарею ИБП</td>
<td>apcaccess status | grep BCHARGE</td>
<td>Раз в квартал</td>
</tr>
<tr>
<td>Обновить систему</td>
<td>apt update && apt upgrade</td>
<td>Раз в месяц</td>
</tr>
<tr>
<td>Проверить логи ошибок</td>
<td>journalctl -p err —since «1 month ago»</td>
<td>Раз в месяц</td>
</tr>
</tbody>
</table>
<p>Отдельно про тест восстановления из бэкапа. Многие делают бэкап годами и ни разу не проверяют можно ли из него восстановиться. Это называется «бэкап Шредингера» — он одновременно и работает, и нет, пока ты его не проверишь. Проверяй. Хотя бы один случайный файл раз в квартал.</p>
<h2>Troubleshooting: симптом — диагноз — решение</h2>
<table>
<thead>
<tr>
<th>Симптом</th>
<th>Диагноз</th>
<th>Команда диагностики</th>
<th>Решение</th>
</tr>
</thead>
<tbody>
<tr>
<td>Диск 100%, сервер лагает</td>
<td>Docker логи или journald</td>
<td>du -sh /var/lib/docker/* && journalctl —disk-usage</td>
<td>docker system prune -f + vacuum journald</td>
</tr>
<tr>
<td>Контейнер не стартует после обновления</td>
<td>Несовместимость данных или образа</td>
<td>docker logs имя —tail 50</td>
<td>Откат к предыдущему тегу образа</td>
</tr>
<tr>
<td>Сервер выключается при отключении света</td>
<td>ИБП с аппроксимированной синусоидой</td>
<td>apcaccess status</td>
<td>Заменить ИБП на Pure Sine Wave</td>
</tr>
<tr>
<td>SSD внезапно только для чтения</td>
<td>Wear indicator критический, диск перешёл в read-only</td>
<td>smartctl -a /dev/sda</td>
<td>Срочная замена диска, восстановление из бэкапа</td>
</tr>
<tr>
<td>Бэкап скрипт молчит, cron работает</td>
<td>Ошибка без уведомления</td>
<td>cat /var/log/restic-backup.log</td>
<td>Добавить проверку exit code и уведомление</td>
</tr>
<tr>
<td>Docker занимает место, prune не помогает</td>
<td>Named volumes без сервисов</td>
<td>docker volume ls -f dangling=true</td>
<td>docker volume prune (осторожно!)</td>
</tr>
<tr>
<td>Высокий I/O write без видимой причины</td>
<td>journald или контейнер с debug-логами</td>
<td>sudo iotop -o</td>
<td>Ограничить journald и настроить log rotation Docker</td>
</tr>
</tbody>
</table>
<h2>Обновление дистрибутива: когда и как делать без потерь</h2>
<h3>Мажорные обновления — не рутина</h3>
<p>Ubuntu 22.04 до 24.04, Debian 11 до 12 — это не просто <code>apt upgrade</code>. Это смена версий Python, systemd, библиотек и компиляторов которые влияют на всё что собрано из исходников или зависит от системных библиотек. На <a title="Docker Compose для домашнего сервера: структура, reverse proxy, секреты и обновления" href="https://it-apteka.com/docker-compose-dlja-domashnego-servera-struktura-reverse-proxy-sekrety-i-obnovlenija/" target="_blank" rel="noopener" data-wpil-monitor-id="2996">домашнем сервере с Docker</a> большинство сервисов изолированы в контейнерах и переживают обновление нормально. Но системные скрипты, cron-задачи и всё что написано на Python под конкретную версию — может сломаться.</p>
<p>Правильная последовательность перед мажорным обновлением дистрибутива:</p>
<ul>
<li>Сделать полный бэкап — не только данных, но и системного раздела</li>
<li>Зафиксировать текущие версии образов всех Docker-контейнеров</li>
<li>Проверить совместимость нового дистрибутива с текущей версией Docker</li>
<li>Убедиться что есть 30-60 минут на проверку после обновления</li>
</ul>
<pre><code class="language-bash">
# Зафиксировать текущие версии контейнеров
docker ps --format "table {{.Names}}\t{{.Image}}" > /root/containers-before-upgrade.txt
# Полный бэкап системы (не только /home и /etc)
restic backup / \
--exclude=/proc --exclude=/sys --exclude=/dev \
--exclude=/tmp --exclude=/run --exclude=/var/run \
--repo /mnt/backup/homelab-system \
--tag "pre-upgrade-$(date +%Y%m%d)"
# Проверить что бэкап успешен
restic snapshots --repo /mnt/backup/homelab-system | tail -3
# Только после этого - обновление
sudo do-release-upgrade
</code></pre>
<p>После обновления первым делом проверяй: Docker запустился, все контейнеры поднялись, сетевые правила UFW в силе, crontab не пустой. Это занимает 5 минут и сразу покажет что сломалось если что-то пошло не так.</p>
<h3>Proxmox без enterprise подписки</h3>
<p>Если используешь Proxmox без платной подписки — репозиторий по умолчанию будет постоянно ругаться на отсутствие авторизации к pve-enterprise.proxmox.com. Это не мешает работе, но раздражает при каждом <code>apt update</code>. Переключись на no-subscription репозиторий:</p>
<pre><code class="language-bash">
# Отключить enterprise репозиторий
sudo sed -i 's/^deb/#deb/g' /etc/apt/sources.list.d/pve-enterprise.list
# Добавить no-subscription (замени bookworm на свою версию если нужно)
echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
| sudo tee /etc/apt/sources.list.d/pve-no-subscription.list
sudo apt update && sudo apt upgrade -y
</code></pre>
<p>No-subscription репозиторий содержит те же пакеты что и enterprise, но с небольшой задержкой после релиза. Для домашнего сервера — абсолютно нормально.</p>
<h2>WireGuard на домашнем сервере: типичная проблема через год</h2>
<h3>Туннель «замерзает» при неактивности</h3>
<p>WireGuard — минималистичный протокол. Он не поддерживает соединение активно — просто шифрует и передаёт пакеты когда они есть. Если трафика нет несколько минут — NAT на маршрутизаторе провайдера или домашнем роутере забывает маппинг портов. Следующий пакет через туннель теряется пока не произойдёт новый handshake.</p>
<p>На практике это выглядит так: VPN «работает», но первые секунды после паузы — пакеты теряются. SSH-сессия через WireGuard зависает после нескольких минут неактивности. Ping идёт, но с потерями после паузы.</p>
<p>Исправляется одной строкой в конфиге:</p>
<pre><code class="language-text">
[Peer]
PublicKey = ...
AllowedIPs = ...
Endpoint = ...
PersistentKeepalive = 25
</code></pre>
<p>PersistentKeepalive = 25 означает: отправлять keepalive пакет каждые 25 секунд даже при отсутствии трафика. Это держит NAT-маппинг активным и не даёт туннелю замерзать. Нужно добавить только со стороны <a title="Настройка NTP MikroTik: клиент, сервер и всё что ломается без него" href="https://it-apteka.com/nastrojka-ntp-na-mikrotik-klient-i-server-shpargalka-dlja-ros-6-i-7/" target="_blank" rel="noopener" data-wpil-monitor-id="3000">клиента за NAT — сервер</a> с публичным IP обычно не требует.</p>
<pre><code class="language-bash">
# Проверить последний handshake (давность в секундах)
sudo wg show wg0 latest-handshakes
# Перезапустить интерфейс после изменения конфига
sudo systemctl restart wg-quick@wg0
sudo wg show
</code></pre>
<p>После добавления PersistentKeepalive latest-handshake должен обновляться каждые ~25 секунд даже без активного трафика.</p>
<h3>Автозапуск WireGuard через systemd</h3>
<p>После перезагрузки сервера WireGuard должен подниматься автоматически. Если использовал wg-quick — это настраивается так:</p>
<pre><code class="language-bash">
sudo systemctl enable wg-quick@wg0
sudo systemctl status wg-quick@wg0
</code></pre>
<p>Если WireGuard работает внутри Docker — убедись что контейнер имеет <code>restart: unless-stopped</code> и что Docker-демон стартует при загрузке системы:</p>
<pre><code class="language-bash">
sudo systemctl enable docker
sudo systemctl is-enabled docker
</code></pre>
<h2>FAQ</h2>
<h3>Как проверить износ SSD на домашнем сервере?</h3>
<p>Установи smartmontools командой <code>sudo apt install smartmontools</code>, потом запусти <code>sudo smartctl -A /dev/sda</code>. Ищи атрибуты SSD_Life_Left (ID 231), Wear_Leveling_Count (ID 177) или Media_Wearout_Indicator (ID 233) — конкретный атрибут зависит от производителя. Для NVMe диска команда: <code>sudo smartctl -a /dev/nvme0 | grep "Percentage Used"</code>. Если значение ниже 70-80% остатка ресурса — диск нужно планово заменить в ближайшие месяцы.</p>
<h3>Почему Docker занял весь диск на сервере?</h3>
<p>Чаще всего виновники — JSON-логи контейнеров в /var/lib/docker/containers/ и старые образы после обновлений. Запусти <code>docker system df</code> чтобы увидеть что именно занимает место, и <code>docker system prune -f</code> для безопасной очистки. Чтобы это не повторялось — настрой лимиты логов в /etc/docker/daemon.json через параметры max-size и max-file.</p>
<h3>Какой ИБП нужен для домашнего сервера?</h3>
<p>Нужен линейно-интерактивный ИБП с чистой синусоидой (Pure Sine Wave) — это критично для современных блоков питания с активным PFC. Бюджетные ИБП со ступенчатой аппроксимацией не подходят и могут повредить БП при переходе на батарею. Обязателен USB-порт для интеграции с apcupsd или NUT, чтобы сервер корректно завершал работу при разряде батареи.</p>
<h3>Как понять что резервная копия действительно работает?</h3>
<p>Запусти <code>restic snapshots</code> чтобы убедиться что снапшоты создаются, и <code>restic check</code> для проверки целостности репозитория. Раз в квартал делай тестовое восстановление: <code>restic restore latest --target /tmp/restore-test</code> и проверь что файлы там есть. Бэкап который ни разу не проверяли на восстановление — это потенциально пустой бэкап.</p>
<h3>Что первым выходит из строя на домашнем сервере за год?</h3>
<p>По опыту — первым ломается не железо, а конфигурация. Заполняется диск из-за неограниченных логов Docker, перестают работать бэкапы из-за молчащих ошибок в скрипте, или контейнер не стартует после автообновления образа. Из железа через год чаще всего обнаруживается неожиданный износ SSD и батарея ИБП которая уже не держит расчётное время.</p>
<h3>Как настроить автоматическую очистку Docker?</h3>
<p>Добавь в crontab безопасную еженедельную очистку: <code>0 3 * * 0 /usr/bin/docker system prune -f</code>. Это удаляет остановленные контейнеры, висячие образы и build cache каждое воскресенье в 3:00, не трогая запущенные сервисы и их данные. Параллельно настрой лимиты логов в daemon.json чтобы предотвратить накопление.</p>
<h3>Почему WireGuard перестаёт работать через несколько минут бездействия?</h3>
<p>Это проблема NAT — маршрутизатор провайдера или домашний роутер забывает маппинг UDP-порта при отсутствии трафика. Добавь в конфиг peer параметр PersistentKeepalive = 25 — это отправляет keepalive каждые 25 секунд и держит соединение живым. Перезапусти интерфейс командой <code>sudo systemctl restart wg-quick@wg0</code> после изменения конфига.</p>
<h3>Сервер не выключается корректно при отключении питания — что делать?</h3>
<p>Проверь три вещи: во-первых, установлен ли apcupsd или NUT и связан ли он с ИБП по USB. Запусти <code>apcaccess status</code> — если ошибка, USB-соединение не настроено. Во-вторых, проверь что в конфиге apcupsd стоят адекватные значения BATTERIES и MINUTES. В-третьих, убедись что тип выходного сигнала ИБП совместим с блоком питания — аппроксимированная синусоида может вызывать некорректное поведение при переходе на батарею.</p>
<h2>Итог</h2>
<p>Домашний сервер через год работы — это не та же машина что ты поставил. Диски пишут постоянно, Docker накапливает мусор, бэкапы незаметно ломаются, ИБП который казался хорошим выдаёт не то что ожидалось. Всё это предсказуемо и диагностируется командами из этой статьи.</p>
<p>Главный урок первого года: домашний сервер требует обслуживания. Не сложного и не постоянного — но регулярного. Раз в месяц проверить логи, раз в квартал запустить smartctl и восстановить файл из бэкапа. Это полчаса в месяц против нескольких часов восстановления при инциденте.</p>
<p>Второй урок — автоматизация без контроля не работает. Cron-задача которая молча падает три месяца выглядит точно так же как cron-задача которая работает. Скрипт бэкапа без проверки exit code и уведомлений — это иллюзия безопасности, а не <a class="wpil_keyword_link" href="https://it-apteka.com/category/security/" target="_blank" rel="noopener" title="Безопасность" data-wpil-keyword-link="linked" data-wpil-monitor-id="3007">безопасность</a>. Каждая автоматическая задача должна либо успешно завершаться с логом, либо явно сообщать об ошибке.</p>
<p>Третий урок — дешёвая экономия дорого стоит. ИБП за 2000 рублей который убивает блок питания. Диск без SMART-мониторинга который падает без предупреждения. Бэкап на тот же диск что и данные. Это не паранойя — это просто понимание что железо ломается, и вопрос только в том, заметишь ли ты это до потери данных.</p>
<p>Если ты сейчас смотришь на свой сервер который работает уже больше года без технического обслуживания — это хороший знак что железо надёжное. Но это не значит что проблем нет. Запусти smartctl, посмотри docker system df, проверь последний бэкап. Скорее всего найдёшь что-нибудь интересное. Лучше сейчас, пока есть время разобраться спокойно.</p>
<p>Инструменты для этого простые: smartmontools, restic, cron, ufw — всё это уже есть в любом стандартном репозитории. Ничего экзотического. Всего несколько часов <a title="VPN на MikroTik: полный гайд 2026 — WireGuard, L2TP/IPsec, IKEv2, настройка сервера и клиента" href="https://it-apteka.com/vpn-na-mikrotik-polnyj-gajd-2026-wireguard-l2tp-ipsec-ikev2-nastrojka-servera-i-klienta/" target="_blank" rel="noopener" data-wpil-monitor-id="3001">настройки дают сервер</a> который можно оставить работать и не бояться вернуться к сюрпризам.</p>
<p>Три вещи которые надо сделать прямо сейчас: проверить износ SSD через smartctl, запустить docker system df и посмотреть на цифры, и восстановить один файл из последнего бэкапа. Не завтра. Сейчас. Если что-то из этого сломалось — лучше узнать в спокойное время чем в три ночи.</p>
"Что
<br />
Если после этих шагов что-то по-прежнему не работает — пиши в комментарии с выводом команды диагностики. Без диагностики помочь сложно, а с выводом smartctl или docker logs — конкретно и быстро.<br />
Быстрый ответ
Вот что ломается на домашнем сервере в первый год работы — по частоте и болезненности:
- Docker съел диск — логи контейнеров без ротации и мусорные образы заполняют раздел до 100%
- SSD изнашивается быстрее ожидаемого — journald и Docker пишут непрерывно, TBW улетает незаметно
- Резервные копии не работали — скрипт был, проверки не было, и в нужный момент бэкап оказался пустым
- ИБП с аппроксимированной синусоидой убил блок питания — ATX БП требует чистую синусоиду
- Контейнеры после обновления перестали стартовать — автообновление без проверки совместимости
Год назад всё было нормально
Поднял домашний сервер. Поставил Proxmox, развернул пяток Docker-контейнеров, настроил Wireguard, добавил Nextcloud и медиасервер. Всё работает. Написал в заметки «сервер готов» и забыл.
Через год — три инцидента за две недели. SSD с неожиданным износом, диск под Docker забит в ноль, резервная копия которая не делалась три месяца, и блок питания который не пережил первого серьезного отключения света. Всё это классика, которую каждый проходит сам — потому что никто об этом не предупреждает заранее.
Эта статья — разбор того, что идёт не так на домашнем сервере через 8-12 месяцев непрерывной работы. С командами диагностики, конкретными цифрами и тем, как это исправить до того, как данные исчезнут.
Речь идёт о типовом стеке: Linux (Debian/Ubuntu или Proxmox), Docker Compose, несколько постоянных контейнеров, SSD как системный диск, работа 24/7.
Проблема первая: SSD износ
Почему домашний сервер убивает SSD быстро
В домашнем ПК SSD живёт годами — потому что пишет мало. Сервер работает иначе. Он пишет постоянно: journald собирает логи ядра и сервисов, Docker пишет stdout всех контейнеров в JSON-файлы, Nextcloud индексирует файлы, базы данных фиксируют транзакции. Это всё непрерывная запись 24/7.
Потребительский SSD на 500 ГБ обычно имеет TBW (Total Bytes Written) в диапазоне 150-300 ТБ. Звучит много. Но при записи 10-20 ГБ в сутки — а это скромная нагрузка для сервера с Docker — ресурс заканчивается через 4-8 лет. Если логирование не ограничено, цифра реально хуже.
Проверь текущий износ. Для SATA SSD:
sudo apt install smartmontools -y
sudo smartctl -A /dev/sda | grep -E "Wear|Life|Written|TBW"
Для NVMe:
sudo smartctl -a /dev/nvme0 | grep -E "Percentage Used|Data Units Written"
Что смотреть в выводе:
| Атрибут SMART |
Значение |
Что означает |
| SSD_Life_Left (ID 231) |
97 = 3% износа, 70 = 30% износа |
Прямой остаток ресурса у Kingston |
| Wear_Leveling_Count (ID 177) |
Чем ниже значение — тем выше износ |
Samsung, другие производители |
| Media_Wearout_Indicator (ID 233) |
Близко к 0 — диск при смерти |
Intel SSD |
| Percentage Used (NVMe) |
0-100%, 100% = исчерпан ресурс |
Все NVMe диски |
| Power_On_Hours (ID 9) |
8760 часов = 1 год работы |
Проверка фактического uptime |
Если SSD_Life_Left уже ниже 80% после первого года — у тебя проблема с интенсивностью записи. Надо искать источник. При этом не паникуй — диск не упадёт завтра. У тебя есть время найти виновника, ограничить запись и планово заменить диск прежде чем он начнёт показывать ошибки. Именно для этого нужен регулярный SMART-мониторинг — чтобы менять диск в рабочее время по своему расписанию, а не в ту ночь когда он решит умереть.
Найди что убивает диск
Установи iotop и посмотри кто пишет прямо сейчас:
sudo apt install iotop -y
sudo iotop -o -d 5
Флаг -o показывает только процессы с активным I/O. Флаг -d 5 обновляет каждые 5 секунд. Смотри колонку DISK WRITE.
Чаще всего виновники три: journald, Docker, и база данных (если Nextcloud или что-то похожее работает без кэша).
Ограничь journald
По умолчанию journald хранит до 10% дискового пространства. На 500 ГБ диске — это 50 ГБ логов. И пишет их непрерывно без ограничений по скорости.
sudo nano /etc/systemd/journald.conf
Добавь или раскомментируй:
[Journal]
SystemMaxUse=500M
SystemMaxFileSize=50M
MaxRetentionSec=1month
RateLimitInterval=30s
RateLimitBurst=10000
Применить без перезагрузки:
sudo systemctl restart systemd-journald
sudo journalctl --vacuum-size=500M
После этого journald будет хранить не больше 500 МБ логов и автоматически ротировать старые записи.
Ограничь логи Docker
Docker по умолчанию пишет stdout каждого контейнера в JSON без каких-либо лимитов. Активный контейнер типа Nextcloud или nginx с access-логами может накопить десятки гигабайт за несколько месяцев.
Глобальный лимит через /etc/docker/daemon.json:
sudo nano /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}
sudo systemctl restart docker
Это применяется к новым контейнерам. Уже запущенные контейнеры нужно пересоздать. Для каждого сервиса в docker-compose.yml можно прописать индивидуально:
services:
nextcloud:
image: nextcloud:latest
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
После этих двух изменений суточная запись на диск падает в 3-5 раз на типовом homelab-стеке. Разница заметна не только по SMART-данным — сервер начинает быстрее отвечать на запросы в периоды активной работы, потому что диск не занят непрерывной записью фоновых логов.
Проблема вторая: Docker съел весь диск
Откуда берутся гигабайты
Через год работы /var/lib/docker превращается в свалку. Остановленные контейнеры, старые образы от обновлений, анонимные тома которые никто не удалял, build cache если ты собирал образы локально. Это всё занимает место молча.
Проверь текущее состояние:
docker system df
Вывод покажет четыре строки: Images, Containers, Volumes, Build Cache — с размером и тем, сколько можно освободить.
Если нужно понять что именно занимает место в /var/lib/docker:
sudo du -sh /var/lib/docker/* | sort -rh | head -10
Самые частые виновники: overlay2 (слои образов и файловые системы контейнеров), containers (JSON-логи), volumes (данные без привязки к сервису).
Очистка по уровням безопасности
Три уровня агрессивности — от безопасного к радикальному.
Уровень 1 — безопасно, ничего живого не трогает:
docker container prune -f
docker image prune -f
docker network prune -f
Удаляет остановленные контейнеры, образы без тегов (dangling), неиспользуемые сети. Volumes не трогает.
Уровень 2 — освобождает больше, но осторожно:
docker system prune -f
То же самое одной командой плюс build cache. Запущенные контейнеры и их образы не трогает.
Уровень 3 — ядерный вариант, только если точно знаешь что делаешь:
docker system prune -a -f --volumes
Удаляет всё включая образы, которые не используются прямо сейчас. После этого все остановленные сервисы придётся пересоздавать с нуля — образы скачаются заново, но данные в named volumes сохранятся если ты их не передал в —volumes.
Важно про логи контейнеров
Если /var/lib/docker/containers занимает много места — это JSON-логи контейнеров. Их можно обнулить без остановки контейнера, но данные за прошлое исчезнут навсегда. После этого обязательно настрой лимиты логов через daemon.json.
# Посмотреть размер логов по контейнерам
sudo du -sh /var/lib/docker/containers/*/*-json.log | sort -rh
# Обнулить все логи (осторожно - потеря истории)
sudo sh -c "truncate -s 0 /var/lib/docker/containers/*/*-json.log"
Автоматическая уборка через cron
Вместо того чтобы чистить вручную раз в полгода — поставь автоуборку раз в неделю. Только безопасный уровень, запущенного не тронет:
sudo crontab -e
# Docker cleanup каждое воскресенье в 3:00
0 3 * * 0 /usr/bin/docker system prune -f >> /var/log/docker-prune.log 2>&1
Проблема третья: резервные копии которых нет
Иллюзия бэкапа
У большинства домашних серверов через год ситуация такая: скрипт написан, cron стоит, но никто ни разу не проверил что внутри архива. А внутри — ошибка монтирования с первого месяца, которую скрипт проглотил и продолжил работу. Технически бэкап «делается» каждую ночь. Фактически он пустой.
Второй сценарий: бэкап делается на тот же диск. Диск умирает — и основные данные, и бэкап уходят вместе. Это не бэкап, это иллюзия безопасности.
Правило 3-2-1 коротко: три копии данных, на двух разных носителях, одна из которых — вне сервера (облако, внешний диск у родственников, VPS).
Restic — минимальный рабочий бэкап
Restic — утилита для инкрементных зашифрованных бэкапов. Один бинарник, работает с локальными директориями, SFTP, S3-совместимым хранилищем. Устанавливается за 30 секунд.
Почему именно restic, а не rsync или простой tar? Restic делает дедупликацию на уровне блоков — если один и тот же файл не менялся, он не копируется заново. Это экономит место и время. Restic шифрует всё на стороне клиента до передачи — даже если бэкап хранится на сервере которому ты не полностью доверяешь, данные защищены. Restic поддерживает ротацию снапшотов с гибкими правилами — держи 7 ежедневных, 4 еженедельных, 6 месячных, остальное автоматически удаляется. И главное — restic позволяет проверить целостность репозитория одной командой и восстановить конкретный файл или папку без разворачивания всего архива.
sudo apt install restic -y
restic version
Инициализируй репозиторий (например, на внешнем диске или в /mnt/backup):
export RESTIC_PASSWORD="твой-пароль-запомни-его"
restic init --repo /mnt/backup/homelab
Первый бэкап важных данных:
restic backup \
/home \
/etc \
/opt/docker-compose \
--repo /mnt/backup/homelab \
--exclude="/home/*/.cache" \
--exclude="/home/*/.local/share/Trash"
Проверь что бэкап реально содержит файлы:
restic snapshots --repo /mnt/backup/homelab
restic ls latest --repo /mnt/backup/homelab | head -30
Это критически важный шаг. Команда restic ls показывает содержимое последнего снапшота. Если там файлы — бэкап работает.
Что бэкапить на Docker-сервере
| Что |
Путь |
Почему важно |
| Docker Compose файлы |
/opt/stacks/ или /home/user/docker/ |
Вся конфигурация сервисов |
| Named volumes |
/var/lib/docker/volumes/ |
Данные баз данных, Nextcloud, и т.д. |
| Конфиги системы |
/etc/ |
Сеть, firewall, cron, systemd юниты |
| Домашние директории |
/home/ |
SSH ключи, .env файлы, скрипты |
| Certs и secrets |
/etc/letsencrypt/, /opt/secrets/ |
Без них сервисы не поднимутся |
Про бэкап Docker Volumes
Перед бэкапом volumes желательно остановить контейнеры которые активно пишут в базу данных. Иначе рискуешь получить инконсистентный снапшот. Для Nextcloud и PostgreSQL это критично.
Автоматизация и ротация
sudo nano /opt/scripts/backup.sh
#!/bin/bash
export RESTIC_REPOSITORY="/mnt/backup/homelab"
export RESTIC_PASSWORD="твой-пароль"
LOG="/var/log/restic-backup.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$DATE] Starting backup..." >> $LOG
# Остановить контейнеры с БД перед бэкапом
docker stop nextcloud_db 2>/dev/null
# Сам бэкап
restic backup \
/home \
/etc \
/opt \
/var/lib/docker/volumes \
--exclude="/var/lib/docker/volumes/*/_data/cache" \
>> $LOG 2>&1
RESULT=$?
# Запустить обратно
docker start nextcloud_db 2>/dev/null
# Удалить старые снапшоты: держать 7 дней, 4 недели, 6 месяцев
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--prune \
>> $LOG 2>&1
if [ $RESULT -eq 0 ]; then
echo "[$DATE] Backup completed successfully" >> $LOG
else
echo "[$DATE] BACKUP FAILED with code $RESULT" >> $LOG
fi
chmod +x /opt/scripts/backup.sh
# Добавить в cron
sudo crontab -e
# Бэкап каждую ночь в 2:00
0 2 * * * /opt/scripts/backup.sh
Проблема четвёртая: ИБП с неправильной синусоидой
Почему дешевый ИБП хуже чем его отсутствие
ИБП покупают чтобы защитить сервер от отключения света. Логично. Но большинство бюджетных ИБП (до 5000-6000 рублей) выдают при переходе на батарею не чистую синусоиду, а ступенчатую аппроксимацию. Выглядит похоже, но для современных блоков питания с активным PFC это проблема.
Блоки питания с активным PFC (это 90% современных ATX блоков) не рассчитаны на ступенчатую аппроксимацию. В лучшем случае БП начинает перегреваться и срабатывает защита — сервер выключается именно тогда когда свет пропал. В худшем — БП выходит из строя через несколько месяцев такой работы.
Проверь свой ИБП: в характеристиках должно быть написано «чистая синусоида» или «Pure Sine Wave». Если написано «аппроксимированная», «ступенчатая» или просто ничего — это не подходит для сервера с нормальным блоком питания.
| Тип выходного сигнала |
Подходит для сервера |
Примерная цена |
| Чистая синусоида (Pure Sine Wave) |
Да, без ограничений |
от 8 000 руб. |
| Ступенчатая аппроксимация |
Нет (для БП с активным PFC) |
2 000 — 5 000 руб. |
| Офлайн ИБП без AVR |
Нет (просадки напряжения) |
до 3 000 руб. |
Для домашнего сервера минимум — линейно-интерактивный ИБП с чистой синусоидой и USB-портом для корректного завершения работы при разряде батареи. APC Back-UPS Pro и CyberPower CP1500PFCLCD из проверенных вариантов.
Настрой автовыключение через apcupsd или NUT
ИБП без интеграции с системой — это просто дорогой удлинитель с батарейкой. Смысл в том, чтобы при разряде батареи сервер завершил работу корректно.
Для APC через USB:
sudo apt install apcupsd -y
sudo nano /etc/apcupsd/apcupsd.conf
UPSNAME homeserver
UPSCABLE usb
UPSTYPE usb
DEVICE
MINUTES 5
TIMEOUT 0
BATTERYLEVEL 30
sudo systemctl enable apcupsd
sudo systemctl start apcupsd
apcaccess status
Параметр MINUTES 5 означает: начать корректное завершение работы если батарея держит меньше 5 минут. BATTERYLEVEL 30 — завершить работу если заряд упал ниже 30%. Оба условия проверяются по OR — что наступит раньше.
Для других ИБП (Ippon, Eaton, Powerware) — смотри Network UPS Tools (NUT), он поддерживает практически всё что подключается по USB или SNMP.
Проблема пятая: контейнеры сломались после обновления
Автообновление без головы
Watchtower, Diun, ручной docker pull по расписанию — все эти инструменты делают одно: обновляют образы контейнеров. Проблема в том, что новая версия образа не всегда совместима с текущими данными или конфигурацией. Особенно это касается баз данных и Nextcloud.
PostgreSQL 15 не читает данные PostgreSQL 14 без явной миграции. Nextcloud требует последовательных обновлений версий. Если автообновление прыгнуло через мажорную версию — контейнер не стартует, а данные целы, но недоступны.
Алгоритм диагностики когда контейнер не поднимается:
# Посмотреть статус всех контейнеров
docker ps -a
# Посмотреть лог проблемного контейнера
docker logs имя_контейнера --tail 50
# Посмотреть события Docker
docker events --since 1h --until now
Типичные ошибки после обновления и что с ними делать:
| Сообщение в логах |
Причина |
Решение |
| database files are incompatible with server |
Мажорное обновление PostgreSQL |
Откатить образ, сделать pg_dump, мигрировать |
| Nextcloud is in maintenance mode |
Обновление не завершило миграцию |
docker exec -u www-data nextcloud php occ upgrade |
| exec /entrypoint.sh: exec format error |
Образ собран под другую архитектуру |
Указать явный тег платформы в compose |
| Permission denied на volume |
Новая версия сменила UID пользователя |
chown -R новый_uid:gid /путь/к/volume |
Как обновлять без риска
Рабочая схема для homelab: перед обновлением бэкап volume, потом обновление, потом проверка. Автоматически это не сделать безопасно — только вручную или с паузой на проверку.
Watchtower в режиме автоматического обновления образов — это удобно для сервисов без состояния. Для сервисов с базами данных, файловыми хранилищами или сложной конфигурацией — только уведомления о новых версиях, а само обновление руками. Diun (Docker Image Update Notifier) хорошо справляется с задачей мониторинга: присылает уведомления когда появляется новый образ, не трогая сами контейнеры.
Для Nextcloud обновление требует отдельного внимания. Нельзя прыгать через мажорные версии — только последовательно. С 25 на 27 напрямую не пройдёт, нужно через 26. Это особенность Nextcloud, и она болезненно проявляется когда Watchtower автоматически обновил образ через несколько версий за период пока ты не смотрел на сервер.
# Зафиксировать текущую версию образа
docker inspect имя_контейнера | grep Image
# Сделать бэкап volume перед обновлением
docker run --rm \
-v имя_volume:/data \
-v /tmp/backup:/backup \
alpine tar czf /backup/volume-backup-$(date +%Y%m%d).tar.gz /data
# Обновить образ
docker pull образ:тег
docker compose up -d имя_сервиса
# Проверить что сервис живой
docker logs имя_контейнера --tail 20
curl -s http://localhost:порт/health
Если что-то пошло не так — откат через pinning версии в docker-compose.yml:
services:
postgres:
image: postgres:15.6 # явный тег, не :latest
Конкретный тег вместо :latest — это единственный способ контролировать что именно запускается. С :latest ты никогда не знаешь что задеплоилось после docker pull.
Архитектура хранения: правильно с самого начала
Одному диску доверять нельзя
Типичная схема homelab: один SSD, на нём система, Docker, данные и бэкапы. Это не схема, это бомба замедленного действия. Когда диск умирает — умирает всё сразу: ОС, контейнеры, данные, и все бэкапы которые ты туда писал.
Минимальная разумная схема для домашнего сервера выглядит иначе:
%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'lineColor': '#64748b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
A["Домашний сервер"] --> B["SSD - система и Docker"]
A --> C["HDD - данные и медиа"]
B --> D["Внешний HDD - локальный бэкап"]
C --> D
D --> E["Облако S3 / VPS - оффсайт бэкап"]
style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style E fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style D fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#c2410c
SSD отвечает за скорость: система, /var/lib/docker, базы данных. HDD отвечает за объём: медиафайлы, архивы, бэкапы. Разделение ролей дисков снижает нагрузку на SSD и даёт понятную схему резервирования.
Разбей разделы правильно на старте
Если ты только разворачиваешь сервер — сделай это правильно сразу. Потом переразбивать без полного переустановки сложно.
| Раздел |
Размер |
Назначение |
| / |
50-80 ГБ |
ОС, системные пакеты |
| /var |
100-200 ГБ |
Docker, логи, journald |
| /home |
остаток SSD |
Конфиги, скрипты, compose-файлы |
| /mnt/data |
весь HDD |
Медиа, файлы, бэкапы |
Отдельный раздел под /var важен потому что именно туда пишут Docker и journald. Если /var переполнится — система остаётся работоспособной, только Docker начнёт ругаться. Если переполняется / — всё гораздо хуже, вплоть до паники ядра.
Смонтировать /var/lib/docker на отдельный диск можно и после установки через symlink или mount —bind, но лучше предусмотреть это заранее.
# Проверить текущую разбивку и использование
lsblk
df -h
# Посмотреть что пишет на диск прямо сейчас
sudo iotop -o -d 3
Безопасность за год незаметно деградирует
Что происходит пока сервер просто работает
Поднял, настроил, забыл. Через год выясняется: SSH всё ещё принимает подключения по паролю, fail2ban стоит но правило для порта Nextcloud так и не добавил, UFW открыт для всей подсети потому что «так было удобнее». Это не катастрофа, но это постепенное снижение планки безопасности которое незаметно накапливается.
Быстрая проверка текущего состояния:
# SSH - что разрешено
sudo grep -E "PasswordAuthentication|PermitRootLogin|Port" /etc/ssh/sshd_config
# UFW - что открыто
sudo ufw status verbose
# Fail2ban - активные правила и статистика
sudo fail2ban-client status
sudo fail2ban-client status sshd
# Последние неудачные попытки входа
sudo lastb | head -20
# Активные сессии
who -a
Что должно быть в /etc/ssh/sshd_config на сервере который торчит в интернет или в локальную сеть с Wi-Fi гостями:
PasswordAuthentication no
PermitRootLogin no
Port 22222
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers твой_пользователь
sudo systemctl restart sshd
После смены порта SSH
Если меняешь порт SSH — сначала открой новый порт в UFW, потом перезапусти sshd, и только потом закрой старый. Иначе заблокируешь себя. Проверяй через второй терминал.
UFW — минимальный firewall без паранойи
# Базовая настройка
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Разрешить только нужное
sudo ufw allow 22222/tcp # SSH на нестандартном порту
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 51820/udp # WireGuard если используешь
# Включить
sudo ufw enable
sudo ufw status numbered
Для сервисов которые должны быть доступны только из локальной сети — ограничь по подсети:
# Nextcloud только из локалки
sudo ufw allow from 192.168.1.0/24 to any port 8080
# Portainer только с твоего IP
sudo ufw allow from 192.168.1.10 to any port 9443
Автоматические обновления безопасности
Обновлять вручную раз в месяц — хорошая привычка. Но патчи безопасности выходят непрерывно, и между твоими обновлениями может оказаться критическая уязвимость. Для homelab разумный компромисс — автоматические обновления только security-пакетов, всё остальное вручную.
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
Это включает автоматическую установку пакетов из репозитория security.ubuntu.com или debian-security. Мажорные обновления и Docker образы — по-прежнему вручную.
Сетевая часть: что проседает незаметно
DNS-резолвер на домашнем сервере
Многие ставят AdGuard Home или Pi-hole на домашний сервер в первый же день. Потом через полгода обнаруживают: DNS перестал отвечать — и не работает вообще ничего в домашней сети, потому что все устройства смотрят на этот сервер. А причина — переполнен диск, контейнер упал, и никто не заметил пока кто-то из домашних не начал жаловаться что интернет «не работает».
Для DNS-сервисов которые влияют на всю сеть — мониторинг обязателен. Минимум: проверка доступности раз в минуту через cron или healthcheck в docker-compose.
services:
adguardhome:
image: adguard/adguardhome:latest
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000"]
interval: 60s
timeout: 10s
retries: 3
start_period: 30s
restart: unless-stopped
Параметр restart: unless-stopped вместо always — важное отличие. При always контейнер будет перезапускаться даже если ты его остановил вручную для обслуживания. При unless-stopped — перезапускается после краша и при старте системы, но не если остановлен явно.
WireGuard: проверка туннеля
WireGuard тихо разрывает соединение если нет трафика и не настроен keepalive. Через несколько часов без активности туннель «замирает» — пинг идёт, но первые пакеты теряются пока не произойдёт повторное согласование ключей. На практике это выглядит как «VPN иногда не работает» без явной причины.
# Проверить состояние всех туннелей
sudo wg show
# Проверить последний handshake (в секундах от текущего времени)
sudo wg show wg0 latest-handshakes
Если latest-handshake больше 3-5 минут при активном использовании — туннель завис. В конфиге peer добавь:
[Peer]
PersistentKeepalive = 25
Это отправляет keepalive каждые 25 секунд и не даёт туннелю замерзать. Особенно важно если WireGuard используется для удалённого доступа к homelab — без этого соединение будет пропадать после нескольких минут неактивности.
Обновление операционной системы: когда и как
Мажорные обновления дистрибутива — отдельная история
Ubuntu 22.04 -> 24.04, Debian 11 -> 12 — это не просто apt upgrade. Это смена версий Python, systemd, ядра, и десятков других компонентов которые могут поломать что-то в твоих контейнерах или системных скриптах. На продакшн-системах такие обновления делают с бэкапом, тестом в staging и окном для отката.
На домашнем сервере — минимум с полным бэкапом системы перед обновлением. Лучший момент для мажорного обновления дистрибутива: когда у тебя есть час-два на проверку что всё работает после, и когда последний бэкап сделан вчера а не три месяца назад.
# Перед мажорным обновлением - сделать полный бэкап
restic backup / --exclude=/proc --exclude=/sys --exclude=/dev \
--exclude=/tmp --exclude=/run \
--repo /mnt/backup/homelab
# Проверить что бэкап успешен
restic snapshots --repo /mnt/backup/homelab | tail -3
# Только потом - обновление дистрибутива
sudo do-release-upgrade
Proxmox: обновления с подпиской и без
Если используешь Proxmox без enterprise подписки — репозиторий по умолчанию будет пытаться обращаться к pve-enterprise.proxmox.com и ругаться на отсутствие авторизации. Это не мешает работе, но надоедает. Переключись на no-subscription репозиторий:
# Отключить enterprise репозиторий
sudo sed -i 's/^deb/#deb/g' /etc/apt/sources.list.d/pve-enterprise.list
# Добавить no-subscription
echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
| sudo tee /etc/apt/sources.list.d/pve-no-subscription.list
sudo apt update && sudo apt upgrade -y
No-subscription репозиторий содержит те же пакеты что и enterprise, просто с небольшой задержкой. Для домашнего сервера это абсолютно нормально.
Питание и тепловой режим: что убивает железо медленно
Перегрев — молчаливый убийца
Домашний сервер часто стоит в шкафу, под столом или в кладовке. Летом температура окружающего воздуха поднимается на 5-10 градусов. Вентиляция которой хватало зимой — уже не справляется. Процессор начинает троттлить, диски греются, и система работает медленнее без видимой причины.
Проверь текущие температуры:
sudo apt install lm-sensors smartmontools -y
sudo sensors-detect --auto
sensors
sudo smartctl -A /dev/sda | grep -i temp
sudo smartctl -a /dev/nvme0 | grep -i temp
Нормальные рабочие температуры для домашнего сервера в простое:
| Компонент |
Норма |
Повод проверить |
Критично |
| Процессор (idle) |
30-50°C |
выше 70°C |
выше 90°C |
| SSD SATA |
25-45°C |
выше 60°C |
выше 70°C |
| NVMe SSD |
35-55°C |
выше 70°C |
выше 85°C |
| HDD |
25-40°C |
выше 50°C |
выше 60°C |
Если NVMe диск регулярно работает выше 70°C — это ускоряет износ и провоцирует ошибки чтения. Радиатор для NVMe стоит 200-400 рублей и ставится за пять минут. Одно из лучших вложений для домашнего сервера.
Энергопотребление и выбор железа
Домашний сервер работает 24/7. Лишние 10 Вт потребления — это 87,6 кВт*ч в год, около 700-900 рублей при текущих тарифах. Разница между Intel N100 (6-10 Вт) и старым Core i7 (65+ Вт) в режиме простоя — это разница в 4000-5000 рублей за электричество в год. Плюс тепловыделение, плюс шум охлаждения.
Включи агрессивное энергосбережение CPU если сервер не занимается вычислениями постоянно:
sudo apt install linux-cpupower -y
sudo cpupower frequency-set -g powersave
# Сделать постоянным
echo 'GOVERNOR="powersave"' | sudo tee /etc/default/cpupower
sudo systemctl enable cpupower
Контейнеры: что идёт не так через год
Orphan volumes накапливаются незаметно
Каждый раз когда удаляешь контейнер через docker rm или docker compose down без флага -v — named volume остаётся. Если потом пересоздаёшь контейнер с другим именем volume — старый висит как мусор. Через год таких «сирот» может накопиться несколько гигабайт.
# Найти volumes без привязки к контейнерам
docker volume ls -f dangling=true
# Посмотреть размер через system df
docker system df -v
Хорошая практика: давай volumes явные имена в docker-compose.yml, тогда легче понять что можно удалить:
volumes:
nextcloud_data:
name: nextcloud_data
nextcloud_db:
name: nextcloud_db
portainer_data:
name: portainer_data
С явными именами docker volume ls показывает понятный список вместо UUID-хэшей. И сразу видно — вот этот volume нужен, а тот непонятный хэш уже полгода не используется.
Контейнер зависает и не реагирует на stop
Редкая, но неприятная ситуация: docker stop контейнер ждёт 10 секунд, посылает SIGKILL, и контейнер всё равно не реагирует. Обычно это означает что процесс завис в uninterruptible sleep — ожидание I/O к диску который перестал отвечать, или ядерный дедлок.
# Узнать PID процесса на хосте
docker inspect имя_контейнера --format '{{.State.Pid}}'
# Проверить dmesg на ошибки диска
sudo dmesg | tail -50 | grep -iE "error|fault|hung_task"
# Принудительное завершение
docker kill --signal=9 имя_контейнера
Если контейнер завис из-за проблем с диском — проверь SMART данные немедленно. Зависание I/O часто предшествует полному отказу диска на несколько дней или недель.
Документируй свою инфраструктуру
Через год ты не помнишь зачем это настроил
Это звучит как совет из учебника для первокурсников, но на практике через год смотришь на свой же crontab и не понимаешь что делает скрипт check2.sh который запускается в 4:17. Или находишь открытый порт в UFW и не помнишь для чего его открывал шесть месяцев назад. Можно было открыть для чего-то временного, закрыть задачу, и забыть закрыть порт.
Минимальная документация которая реально спасает:
- README.md в папке с compose-файлами — что запущено, зачем, как перезапустить
- Комментарии в crontab перед каждой задачей:
# Проверка износа SSD раз в неделю
- Changelog в один файл — дата, что изменил, зачем
- Git для конфигов — история изменений и возможность откатить
cd /opt/stacks
git init
git add .
git commit -m "initial: базовый стек после первоначальной настройки"
# После каждого значимого изменения
git add docker-compose.yml
git commit -m "nextcloud: обновил до 28.0.1, добавил redis"
Не нужен GitHub или GitLab — локальный репозиторий достаточен. Главное что у тебя есть история и возможность сделать git diff HEAD~1 когда что-то перестало работать после правки конфига. В три ночи это экономит нервы.
Безопасность за год незаметно деградирует
Что происходит пока сервер просто работает
Поднял, настроил, забыл. Через год выясняется: SSH всё ещё принимает подключения по паролю, fail2ban стоит но правило для порта Nextcloud так и не добавил, UFW открыт для всей подсети потому что «так было удобнее при настройке». Это не катастрофа сегодня, но это постепенное снижение планки безопасности которое незаметно накапливается.
Быстрая проверка текущего состояния:
# SSH - что разрешено
sudo grep -E "PasswordAuthentication|PermitRootLogin|Port" /etc/ssh/sshd_config
# UFW - что открыто
sudo ufw status verbose
# Fail2ban - активные правила
sudo fail2ban-client status
# Последние неудачные попытки входа по SSH
sudo lastb | head -20
Что должно быть в /etc/ssh/sshd_config на сервере который доступен снаружи или из гостевой Wi-Fi сети:
PasswordAuthentication no
PermitRootLogin no
MaxAuthTries 3
AllowUsers твой_пользователь
sudo systemctl restart sshd
После изменений SSH
Перед перезапуском sshd с новыми настройками — держи открытую сессию. Проверь новое подключение в отдельном терминале. Только если зашло — закрывай старую сессию. Иначе рискуешь заблокировать себя на своём же сервере.
UFW — минимальный firewall
Базовая настройка которая закрывает всё лишнее:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 51820/udp # WireGuard
sudo ufw enable
sudo ufw status numbered
Для сервисов которые должны быть доступны только из локальной сети — ограничь по подсети:
# Portainer только из локалки
sudo ufw allow from 192.168.1.0/24 to any port 9443
# Prometheus только с конкретного IP
sudo ufw allow from 192.168.1.10 to any port 9090
Системные требования
| Компонент |
Минимум |
Рекомендуется |
Актуальная версия |
| ОС |
Ubuntu 22.04 LTS / Debian 11 |
Ubuntu 24.04 LTS / Debian 12 |
Ubuntu 24.04.2 / Debian 12.6 |
| Docker Engine |
24.x |
26.x и выше |
27.x |
| Docker Compose |
v2.15 |
v2.24+ |
v2.27+ |
| smartmontools |
7.2 |
7.4 |
7.4 |
| restic |
0.14 |
0.16+ |
0.17.x |
| Proxmox VE |
7.x |
8.x |
8.2 |
На момент публикации актуальны версии указанные выше. Перед установкой или обновлением проверяй свежие релизы на официальных страницах — особенно для Docker и restic, они обновляются часто.
Мониторинг: знать до того как сломалось
Минимальный стек без оверинжиниринга
Для homelab не нужен Prometheus + Grafana + Alertmanager. Нужно получить уведомление когда диск заполнен на 85%, контейнер упал, или SSD начал сигнализировать. Это решается тремя утилитами.
%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'lineColor': '#64748b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
A["Домашний сервер 24/7"] --> B["smartd - мониторинг дисков"]
A --> C["cron + скрипт - диск и контейнеры"]
A --> D["apcupsd - состояние ИБП"]
B --> E["Email / Telegram алерт"]
C --> E
D --> E
E --> F["Ты узнал до падения"]
style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style F fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style E fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#c2410c
Smartd уже входит в пакет smartmontools. Настрой его на автоматическую проверку дисков и уведомление при проблемах:
sudo nano /etc/smartd.conf
# Мониторинг всех дисков, короткий тест раз в сутки, длинный раз в неделю
DEVICESCAN -a -o on -S on -n standby,q \
-s (S/../.././02|L/../../6/03) \
-W 4,45,50 \
-m root -M exec /usr/share/smartmontools/smartd-runner
sudo systemctl enable smartd
sudo systemctl restart smartd
Простой скрипт мониторинга диска и контейнеров с отправкой в системный лог (или адаптируй под Telegram webhook):
#!/bin/bash
# /opt/scripts/check-health.sh
THRESHOLD_DISK=85
# Проверка диска
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$DISK_USAGE" -gt "$THRESHOLD_DISK" ]; then
logger -t homelab-check "WARNING: Root disk usage is ${DISK_USAGE}%"
fi
# Проверка Docker контейнеров
STOPPED=$(docker ps -a --filter "status=exited" --format "{{.Names}}" | tr '\n' ' ')
if [ -n "$STOPPED" ]; then
logger -t homelab-check "WARNING: Stopped containers: $STOPPED"
fi
# Проверка SSD износа (Kingston/Intel)
SSD_LIFE=$(sudo smartctl -A /dev/sda | grep -E "SSD_Life_Left|Media_Wearout" | awk '{print $4}' | head -1)
if [ -n "$SSD_LIFE" ] && [ "$SSD_LIFE" -lt 70 ]; then
logger -t homelab-check "WARNING: SSD life remaining: ${SSD_LIFE}%"
fi
chmod +x /opt/scripts/check-health.sh
# Запускать каждый час
echo "0 * * * * /opt/scripts/check-health.sh" | sudo crontab -
Результаты скрипта видны в sudo journalctl -t homelab-check. Если хочешь Telegram — замени logger на curl с bot API, это 10 минут работы.
Профилактика: что делать раз в квартал
Ежемесячные задачи
Раз в месяц уходит 15-20 минут. Это минимум который не даёт накапливаться проблемам до критического уровня. Обновление системы и пакетов — первое что стоит делать регулярно. Не мажорные обновления дистрибутива, а просто apt update && apt upgrade. Это закрывает уязвимости в безопасности которые появляются постоянно, и обновляет утилиты вроде smartmontools и restic.
sudo apt update
sudo apt upgrade -y
sudo apt autoremove -y
Проверка логов на ошибки — занимает две минуты но может показать проблему которая накапливается незаметно:
sudo journalctl -p err --since "1 month ago" | tail -50
sudo dmesg | grep -iE "error|fail|bad sector" | tail -20
Квартальные задачи
Один раз потратить час раз в три месяца — это намного лучше чем восстанавливать сервер после инцидента.
| Задача |
Команда |
Периодичность |
| Проверить износ SSD |
smartctl -A /dev/sda |
Раз в квартал |
| Проверить бэкап |
restic snapshots && restic check |
Раз в месяц |
| Протестировать восстановление |
restic restore latest —target /tmp/restore-test |
Раз в квартал |
| Очистить Docker |
docker system prune -f |
Раз в месяц (или cron) |
| Проверить батарею ИБП |
apcaccess status | grep BCHARGE |
Раз в квартал |
| Обновить систему |
apt update && apt upgrade |
Раз в месяц |
| Проверить логи ошибок |
journalctl -p err —since «1 month ago» |
Раз в месяц |
Отдельно про тест восстановления из бэкапа. Многие делают бэкап годами и ни разу не проверяют можно ли из него восстановиться. Это называется «бэкап Шредингера» — он одновременно и работает, и нет, пока ты его не проверишь. Проверяй. Хотя бы один случайный файл раз в квартал.
Troubleshooting: симптом — диагноз — решение
| Симптом |
Диагноз |
Команда диагностики |
Решение |
| Диск 100%, сервер лагает |
Docker логи или journald |
du -sh /var/lib/docker/* && journalctl —disk-usage |
docker system prune -f + vacuum journald |
| Контейнер не стартует после обновления |
Несовместимость данных или образа |
docker logs имя —tail 50 |
Откат к предыдущему тегу образа |
| Сервер выключается при отключении света |
ИБП с аппроксимированной синусоидой |
apcaccess status |
Заменить ИБП на Pure Sine Wave |
| SSD внезапно только для чтения |
Wear indicator критический, диск перешёл в read-only |
smartctl -a /dev/sda |
Срочная замена диска, восстановление из бэкапа |
| Бэкап скрипт молчит, cron работает |
Ошибка без уведомления |
cat /var/log/restic-backup.log |
Добавить проверку exit code и уведомление |
| Docker занимает место, prune не помогает |
Named volumes без сервисов |
docker volume ls -f dangling=true |
docker volume prune (осторожно!) |
| Высокий I/O write без видимой причины |
journald или контейнер с debug-логами |
sudo iotop -o |
Ограничить journald и настроить log rotation Docker |
Обновление дистрибутива: когда и как делать без потерь
Мажорные обновления — не рутина
Ubuntu 22.04 до 24.04, Debian 11 до 12 — это не просто apt upgrade. Это смена версий Python, systemd, библиотек и компиляторов которые влияют на всё что собрано из исходников или зависит от системных библиотек. На домашнем сервере с Docker большинство сервисов изолированы в контейнерах и переживают обновление нормально. Но системные скрипты, cron-задачи и всё что написано на Python под конкретную версию — может сломаться.
Правильная последовательность перед мажорным обновлением дистрибутива:
- Сделать полный бэкап — не только данных, но и системного раздела
- Зафиксировать текущие версии образов всех Docker-контейнеров
- Проверить совместимость нового дистрибутива с текущей версией Docker
- Убедиться что есть 30-60 минут на проверку после обновления
# Зафиксировать текущие версии контейнеров
docker ps --format "table {{.Names}}\t{{.Image}}" > /root/containers-before-upgrade.txt
# Полный бэкап системы (не только /home и /etc)
restic backup / \
--exclude=/proc --exclude=/sys --exclude=/dev \
--exclude=/tmp --exclude=/run --exclude=/var/run \
--repo /mnt/backup/homelab-system \
--tag "pre-upgrade-$(date +%Y%m%d)"
# Проверить что бэкап успешен
restic snapshots --repo /mnt/backup/homelab-system | tail -3
# Только после этого - обновление
sudo do-release-upgrade
После обновления первым делом проверяй: Docker запустился, все контейнеры поднялись, сетевые правила UFW в силе, crontab не пустой. Это занимает 5 минут и сразу покажет что сломалось если что-то пошло не так.
Proxmox без enterprise подписки
Если используешь Proxmox без платной подписки — репозиторий по умолчанию будет постоянно ругаться на отсутствие авторизации к pve-enterprise.proxmox.com. Это не мешает работе, но раздражает при каждом apt update. Переключись на no-subscription репозиторий:
# Отключить enterprise репозиторий
sudo sed -i 's/^deb/#deb/g' /etc/apt/sources.list.d/pve-enterprise.list
# Добавить no-subscription (замени bookworm на свою версию если нужно)
echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
| sudo tee /etc/apt/sources.list.d/pve-no-subscription.list
sudo apt update && sudo apt upgrade -y
No-subscription репозиторий содержит те же пакеты что и enterprise, но с небольшой задержкой после релиза. Для домашнего сервера — абсолютно нормально.
WireGuard на домашнем сервере: типичная проблема через год
Туннель «замерзает» при неактивности
WireGuard — минималистичный протокол. Он не поддерживает соединение активно — просто шифрует и передаёт пакеты когда они есть. Если трафика нет несколько минут — NAT на маршрутизаторе провайдера или домашнем роутере забывает маппинг портов. Следующий пакет через туннель теряется пока не произойдёт новый handshake.
На практике это выглядит так: VPN «работает», но первые секунды после паузы — пакеты теряются. SSH-сессия через WireGuard зависает после нескольких минут неактивности. Ping идёт, но с потерями после паузы.
Исправляется одной строкой в конфиге:
[Peer]
PublicKey = ...
AllowedIPs = ...
Endpoint = ...
PersistentKeepalive = 25
PersistentKeepalive = 25 означает: отправлять keepalive пакет каждые 25 секунд даже при отсутствии трафика. Это держит NAT-маппинг активным и не даёт туннелю замерзать. Нужно добавить только со стороны клиента за NAT — сервер с публичным IP обычно не требует.
# Проверить последний handshake (давность в секундах)
sudo wg show wg0 latest-handshakes
# Перезапустить интерфейс после изменения конфига
sudo systemctl restart wg-quick@wg0
sudo wg show
После добавления PersistentKeepalive latest-handshake должен обновляться каждые ~25 секунд даже без активного трафика.
Автозапуск WireGuard через systemd
После перезагрузки сервера WireGuard должен подниматься автоматически. Если использовал wg-quick — это настраивается так:
sudo systemctl enable wg-quick@wg0
sudo systemctl status wg-quick@wg0
Если WireGuard работает внутри Docker — убедись что контейнер имеет restart: unless-stopped и что Docker-демон стартует при загрузке системы:
sudo systemctl enable docker
sudo systemctl is-enabled docker
FAQ
Как проверить износ SSD на домашнем сервере?
Установи smartmontools командой sudo apt install smartmontools, потом запусти sudo smartctl -A /dev/sda. Ищи атрибуты SSD_Life_Left (ID 231), Wear_Leveling_Count (ID 177) или Media_Wearout_Indicator (ID 233) — конкретный атрибут зависит от производителя. Для NVMe диска команда: sudo smartctl -a /dev/nvme0 | grep "Percentage Used". Если значение ниже 70-80% остатка ресурса — диск нужно планово заменить в ближайшие месяцы.
Почему Docker занял весь диск на сервере?
Чаще всего виновники — JSON-логи контейнеров в /var/lib/docker/containers/ и старые образы после обновлений. Запусти docker system df чтобы увидеть что именно занимает место, и docker system prune -f для безопасной очистки. Чтобы это не повторялось — настрой лимиты логов в /etc/docker/daemon.json через параметры max-size и max-file.
Какой ИБП нужен для домашнего сервера?
Нужен линейно-интерактивный ИБП с чистой синусоидой (Pure Sine Wave) — это критично для современных блоков питания с активным PFC. Бюджетные ИБП со ступенчатой аппроксимацией не подходят и могут повредить БП при переходе на батарею. Обязателен USB-порт для интеграции с apcupsd или NUT, чтобы сервер корректно завершал работу при разряде батареи.
Как понять что резервная копия действительно работает?
Запусти restic snapshots чтобы убедиться что снапшоты создаются, и restic check для проверки целостности репозитория. Раз в квартал делай тестовое восстановление: restic restore latest --target /tmp/restore-test и проверь что файлы там есть. Бэкап который ни разу не проверяли на восстановление — это потенциально пустой бэкап.
Что первым выходит из строя на домашнем сервере за год?
По опыту — первым ломается не железо, а конфигурация. Заполняется диск из-за неограниченных логов Docker, перестают работать бэкапы из-за молчащих ошибок в скрипте, или контейнер не стартует после автообновления образа. Из железа через год чаще всего обнаруживается неожиданный износ SSD и батарея ИБП которая уже не держит расчётное время.
Как настроить автоматическую очистку Docker?
Добавь в crontab безопасную еженедельную очистку: 0 3 * * 0 /usr/bin/docker system prune -f. Это удаляет остановленные контейнеры, висячие образы и build cache каждое воскресенье в 3:00, не трогая запущенные сервисы и их данные. Параллельно настрой лимиты логов в daemon.json чтобы предотвратить накопление.
Почему WireGuard перестаёт работать через несколько минут бездействия?
Это проблема NAT — маршрутизатор провайдера или домашний роутер забывает маппинг UDP-порта при отсутствии трафика. Добавь в конфиг peer параметр PersistentKeepalive = 25 — это отправляет keepalive каждые 25 секунд и держит соединение живым. Перезапусти интерфейс командой sudo systemctl restart wg-quick@wg0 после изменения конфига.
Сервер не выключается корректно при отключении питания — что делать?
Проверь три вещи: во-первых, установлен ли apcupsd или NUT и связан ли он с ИБП по USB. Запусти apcaccess status — если ошибка, USB-соединение не настроено. Во-вторых, проверь что в конфиге apcupsd стоят адекватные значения BATTERIES и MINUTES. В-третьих, убедись что тип выходного сигнала ИБП совместим с блоком питания — аппроксимированная синусоида может вызывать некорректное поведение при переходе на батарею.
Итог
Домашний сервер через год работы — это не та же машина что ты поставил. Диски пишут постоянно, Docker накапливает мусор, бэкапы незаметно ломаются, ИБП который казался хорошим выдаёт не то что ожидалось. Всё это предсказуемо и диагностируется командами из этой статьи.
Главный урок первого года: домашний сервер требует обслуживания. Не сложного и не постоянного — но регулярного. Раз в месяц проверить логи, раз в квартал запустить smartctl и восстановить файл из бэкапа. Это полчаса в месяц против нескольких часов восстановления при инциденте.
Второй урок — автоматизация без контроля не работает. Cron-задача которая молча падает три месяца выглядит точно так же как cron-задача которая работает. Скрипт бэкапа без проверки exit code и уведомлений — это иллюзия безопасности, а не безопасность. Каждая автоматическая задача должна либо успешно завершаться с логом, либо явно сообщать об ошибке.
Третий урок — дешёвая экономия дорого стоит. ИБП за 2000 рублей который убивает блок питания. Диск без SMART-мониторинга который падает без предупреждения. Бэкап на тот же диск что и данные. Это не паранойя — это просто понимание что железо ломается, и вопрос только в том, заметишь ли ты это до потери данных.
Если ты сейчас смотришь на свой сервер который работает уже больше года без технического обслуживания — это хороший знак что железо надёжное. Но это не значит что проблем нет. Запусти smartctl, посмотри docker system df, проверь последний бэкап. Скорее всего найдёшь что-нибудь интересное. Лучше сейчас, пока есть время разобраться спокойно.
Инструменты для этого простые: smartmontools, restic, cron, ufw — всё это уже есть в любом стандартном репозитории. Ничего экзотического. Всего несколько часов настройки дают сервер который можно оставить работать и не бояться вернуться к сюрпризам.
Три вещи которые надо сделать прямо сейчас: проверить износ SSD через smartctl, запустить docker system df и посмотреть на цифры, и восстановить один файл из последнего бэкапа. Не завтра. Сейчас. Если что-то из этого сломалось — лучше узнать в спокойное время чем в три ночи.
Что делать если не помогло
Если после этих шагов что-то по-прежнему не работает — пиши в комментарии с выводом команды диагностики. Без диагностики помочь сложно, а с выводом smartctl или docker logs — конкретно и быстро.