Права, символьные ссылки и xattr: что теряется при переносе данных
Перенос данных на новый сервер почти всегда заканчивается фразой «всё скопировалось, размер совпадает» — и именно в этот момент упускается то, что размер файла не показывает. Права доступа, символьные ссылки и расширенные атрибуты не входят в объём данных, но именно от них зависит, заработает ли приложение после переноса или начнёт падать с непонятными ошибками доступа. Ниже — что именно теряется и как проверить, что не потерялось.
Содержание
Почему совпадение размера ничего не гарантирует
Контрольная сумма или размер файла отвечают на вопрос «то же самое содержимое?» — и всё. Они ничего не говорят о том, кто владеет файлом, какие у него права, куда ведёт символьная ссылка и какие дополнительные метаданные к нему прикреплены. Это разные слои информации, и большинство инструментов копирования по умолчанию переносят только содержимое — байты внутри файла, — а метаданные вокруг файла сохраняют лишь при явном запросе.
Проблема в том, что «явный запрос» — это не то, что происходит по умолчанию в большинстве утилит. cp -r без флагов, архивация через некоторые GUI-клиенты, наивные скрипты на rsync без -a — всё это копирует содержимое, но по-своему обращается с правами, ссылками и атрибутами. Итог виден не сразу: сайт запускается, но веб-сервер не может писать в upload-директорию; резервная копия занимает вдвое больше места, потому что символьные ссылки превратились в полные копии файлов; приложение теряет метки, по которым отличало обработанные файлы от новых.
Один из практических разборов такой ситуации — перенос 2 ТБ данных, где rsync без одного флага отчитался об успехе, а приложение перестало читать собственные файлы: владелец и группа не сохранились, и на новом сервере файлы принадлежали не тому пользователю.
Права доступа и владелец файла
У каждого файла в Linux есть владелец (user), группа (group) и набор прав (read/write/execute для владельца, группы и остальных). Это не часть содержимого файла — это отдельные метаданные, которые хранятся в inode файловой системы. При копировании они не переносятся автоматически «просто потому что скопировался файл» — их либо явно сохраняют, либо инструмент назначает новые значения по своим правилам.
Типичное поведение без явного сохранения прав:
- владельцем и группой становится пользователь, от имени которого запущено копирование (обычно тот, кем вы залогинены на новом сервере);
- права выставляются по
umaskтекущей сессии, а не берутся из исходного файла; - setuid/setgid-биты и биты sticky могут молча слетать — часть инструментов их просто не переносит.
Для приложений это означает: если веб-сервер работал от пользователя www-data и владел директорией с загрузками, а после переноса владельцем стал root или личный аккаунт администратора, — сервис либо не сможет писать в эту директорию, либо (что хуже) получит более широкие права, чем задумано.
Проверить текущего владельца и права после переноса:
stat -c '%U:%G %a %n' /var/www/app/uploads
# ожидаемо: www-data:www-data 755 /var/www/app/uploads
Если владелец не совпадает с ожидаемым — на новом сервере может вообще не существовать пользователя с тем же UID, что и на старом (если пользователей создавали в разное время и в разном порядке). В этом случае даже правильно сохранённые числовые UID/GID отобразятся как «чужие» имена, пока вы не заведёте пользователей с теми же UID на приёмнике.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверСимвольные ссылки: копия ссылки или копия содержимого
Символьная ссылка (symlink) — это отдельный маленький файл, который хранит путь к другому файлу или директории. Она не содержит данных, на которые указывает, — только путь. И здесь ключевая развилка при копировании: инструмент может либо скопировать саму ссылку (сохранить путь), либо перейти по ссылке и скопировать то, на что она указывает.
Второе поведение — «разыменование» ссылки — часто оказывается поведением по умолчанию у инструментов, которые не задумывались специально как средство миграции файловых систем. Последствия предсказуемы:
- вместо лёгкой ссылки на новом сервере появляется полноразмерная копия целевого файла — место на диске уходит незапланированно, особенно если ссылка указывала на что-то крупное;
- если ссылка была относительной и указывала за пределы копируемого дерева (например, на системную библиотеку), при разыменовании в копию попадёт файл, которого там быть не должно, либо копирование просто упадёт с ошибкой «файл не найден»;
- если ссылка была битой (указывала на несуществующий путь) — некоторые инструменты по умолчанию просто пропускают такие файлы молча, без ошибки, и вы не узнаете, что что-то отсутствует, пока приложение не начнёт падать.
Разница между тем, за что «держится» жёсткая ссылка и символьная, и почему это критично именно при переносе между файловыми системами, подробно разобрана в статье про жёсткую и символическую ссылку изнутри — если в переносимых данных есть ссылки на файлы вне копируемого дерева, стоит прочитать её до миграции, а не после.
Проверить, что символьные ссылки перенеслись как ссылки, а не превратились в файлы:
find /var/www/app -type l -exec sh -c 'echo "$1 -> $(readlink "$1")"' _ {} \;
Сравните вывод на старом и новом сервере — количество ссылок и то, куда они указывают, должно совпасть.
Расширенные атрибуты (xattr)
Расширенные атрибуты — это механизм файловой системы, который позволяет прикрепить к файлу дополнительные пары «ключ-значение», не относящиеся к обычным правам доступа. Это не экзотика: xattr использует SELinux для меток безопасности, ACL (списки контроля доступа) во многих реализациях хранятся как xattr, macOS хранит в них метаданные Finder (com.apple.quarantine и подобные), некоторые приложения и антивирусы помечают файлы через xattr как проверенные или обработанные.
Особенность xattr в контексте переноса — этот механизм известен и документирован, но не все инструменты копирования читают и переносят его по умолчанию, потому что xattr исторически не считался «обязательной» частью файла — это опциональное расширение, которое каждая файловая система и каждый инструмент поддерживает в разной степени. Итог тот же, что и с правами: файл скопируется, xattr — не обязательно.
Посмотреть, есть ли у файла расширенные атрибуты и что в них:
getfattr -d /etc/nginx/nginx.conf
# или на конкретный атрибут:
getfattr -n security.selinux /etc/nginx/nginx.conf
Если вывод пустой — атрибутов нет, и беспокоиться не о чем. Если что-то есть — стоит явно проверить, что это же значение появилось на приёмнике после переноса, потому что молча потерянный SELinux-контекст объясняет добрую половину загадочных «Permission denied» на системах, где SELinux в enforcing-режиме, хотя обычные unix-права выглядят абсолютно правильными.
Практический подход: не полагаться на умолчания
Главный практический вывод простой: для переноса данных, где права, ссылки и атрибуты имеют значение, нужно явно указывать инструменту сохранить всё это — а не надеяться, что поведение по умолчанию совпадает с ожиданиями. У rsync для этого есть набор широко известных флагов:
rsync -avAX --numeric-ids -e ssh /var/www/app/ user@new-server:/var/www/app/
Разбор флагов:
| Флаг | Что делает |
|---|---|
-a (archive) | Включает -rlptgoD: рекурсия, сохранение символьных ссылок как ссылок, прав, владельца, группы, времени, устройств |
-A | Сохраняет ACL (списки контроля доступа), если они используются |
-X | Сохраняет расширенные атрибуты (xattr) |
--numeric-ids | Переносит UID/GID как числа, а не пытается сопоставить их по именам пользователей, которых на новом сервере может не быть |
Без -A и -X архивный режим -a сохранит базовые unix-права, владельца и символьные ссылки, но не тронет ACL и xattr — это отдельные флаги специально потому, что не на всех файловых системах они вообще поддерживаются (например, при копировании на FAT32-раздел -X не даст ничего, потому что там xattr нет как класса).
Для tar аналогичная логика — по умолчанию tar сохраняет права и владельца при упаковке от root, но xattr и ACL нужно попросить явно:
tar --acls --xattrs -czf archive.tar.gz /var/www/app/
tar --acls --xattrs -xzf archive.tar.gz -C /var/www/app/
Если перенос идёт между серверами с разным составом пользователей (UID/GID не совпадают по смыслу), --numeric-ids в rsync — не всегда правильный выбор: иногда лучше сначала синхронизировать пользователей и группы на приёмнике, чтобы числовые ID означали тех же людей и сервисы, что и на источнике, а уже потом копировать данные с сохранением владельца.
Проверка после переноса: что сверить в первую очередь
Совпадение размера и контрольной суммы содержимого — необходимое, но не достаточное условие успешного переноса. Отдельно стоит проверить:
- Права и владелец критичных путей — директорий, куда приложение пишет (загрузки, логи, кэш, сокеты), и файлов с ограниченным доступом (приватные ключи, конфиги с паролями). Разница в правах на приватный ключ TLS — это либо сервис, который не стартует, либо ключ, который читают все.
- Символьные ссылки — количество, и куда каждая указывает. Особенно важно для конфигураций с симлинками на «текущий релиз» (паттерн деплоя через
current -> releases/2026-08-28), где потерянная или разыменованная ссылка ломает весь деплой. - xattr на файлах, где они использовались — если на источнике был включён SELinux или ACL, сверить хотя бы выборочно несколько файлов из разных директорий.
- Права на уровне директорий, а не только файлов — execute-бит на директории определяет, можно ли в неё вообще зайти, и его тоже теряют при небрежном копировании.
Быстрая сверка прав и владельца между старым и новым сервером:
# на старом сервере
find /var/www/app -printf '%p %u:%g %m\n' | sort > /tmp/before.txt
# на новом сервере (тот же путь)
find /var/www/app -printf '%p %u:%g %m\n' | sort > /tmp/after.txt
# сравнение
diff /tmp/before.txt /tmp/after.txt
Пустой вывод diff — хороший знак: пути, владельцы и права совпали построчно. Если контрольные суммы содержимого тоже нужно перепроверить отдельно от факта «код возврата 0», это отдельная и не менее частая проблема — разбор описан в статье о том, как rsync отчитался об успехе, а файл на приёмнике оказался другим. Общий чек-лист проверки после миграции сервера (не только метаданных файлов, но и доступности сервисов, cron-задач, интеграций) собран в статье «как проверить, что миграция прошла успешно».
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Если копирую через scp, права тоже теряются?
По умолчанию scp сохраняет базовые unix-права и время модификации файла, но не переносит ACL и xattr. Для простого переноса нескольких файлов это обычно не критично, но для полноценной миграции директорий с приложением лучше использовать rsync -avAX — он и надёжнее при обрыве соединения, и переносит больше метаданных явно.
Как узнать, есть ли у моих файлов xattr, прежде чем переносить?
Пройтись по дереву и проверить каждый файл на непустой вывод getfattr: find /var/www/app -type f -exec sh -c 'getfattr -d "$1" 2>/dev/null | grep -q . && echo "$1"' _ {} \;. Если список пустой — xattr в данных нет, и флаг -X можно не указывать, хотя лишним он не будет.
Почему после переноса приложение видит файлы, но не может их читать?
Самая частая причина — несовпадение владельца: сервис запущен от одного пользователя, а файлы после копирования принадлежат другому (обычно тому, кем вы подключились по SSH на новый сервер). Проверяется через stat -c '%U:%G' файл и сверку с тем, от какого пользователя реально работает сервис (ps aux | grep имя_сервиса).
Символьная ссылка на новом сервере ведёт «в никуда» — это норма?
Нормально, если целевой путь ссылки был абсолютным и указывал за пределы перенесённого дерева (например, на системную библиотеку с другого пути). Если ссылка была относительной внутри одной директории с приложением — она должна работать так же, как на старом сервере; если не работает, вероятно, ссылку разыменовали при копировании и вместо неё скопировалось содержимое.
Нужно ли отдельно переносить ACL, если я уже указал -a в rsync?
Нет, -a их не включает — ACL сохраняются отдельным флагом -A. Это осознанное разделение в rsync: не каждая файловая система и не каждый сценарий переноса нуждается в ACL, поэтому флаг вынесен отдельно, а не встроен в архивный режим по умолчанию.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →