Как делать11 октября 2026 г.10 мин чтения
Docker и деплойШаг 2 из 8

Linux для разработчика: файлы, права, процессы, SSH

Коротко

Чтобы уверенно работать с Linux-сервером, разработчику нужно пять навыков: входить по SSH-ключу, работать от обычного пользователя с sudo, понимать владельцев и права файлов, находить процессы и занятые порты и запускать приложение как сервис systemd с журналом в journalctl. Ниже — вся первичная настройка VPS на Ubuntu 24.04 LTS с реальным выводом команд и итоговым чек-листом.

Пошаговый урокУровень: НачальныйLinuxПрактика: ~45–90 мин

После материала вы сможете

  • создать SSH-ключ ed25519, сверить отпечаток сервера и войти по псевдониму из ~/.ssh/config
  • завести пользователя с sudo, исправить права ~/.ssh и найти причину отказа доступа через journalctl и namei -l
  • найти процесс и занятый порт через pgrep, ps и ss -tlnp и проверить ответ приложения через curl
  • описать приложение как сервис systemd с перезапуском, читать его журнал и закрыть вход по паролю с проверкой sshd -t

Проверено на

Ubuntu 24.04.5 LTSOpenSSH 9.6p1systemd 255bash 5.2ufw 0.36.2Python 3.12 (http.server)Node.js 24.18

Сверено с документацией: 11 октября 2026 г.

Содержание
  1. 01Сервер изнутри: где что лежит
  2. 02SSH: вход по ключу вместо пароля
  3. 03Пользователи, группы и sudo
  4. 04Владельцы и права на сервере: chown, umask, namei
  5. 05Процессы, порты и curl
  6. 06systemd: приложение как сервис и журналы
  7. 07Пакеты, файрвол и вход только по ключу
  8. 08Чек-лист первичной настройки и упражнения

Сервер изнутри: где что лежит

VPS — это удалённый компьютер с Linux без графического интерфейса: всё делается командами через SSH. Урок продолжает «Терминал и командная строка», где уже разобраны навигация, перенаправления и основы chmod. Здесь — то, что появляется только на сервере: другие пользователи, сервисы, сеть и безопасность. Примеры проверены на Ubuntu 24.04.5 LTS. LTS-выпуски Ubuntu получают пять лет стандартных обновлений безопасности, для 24.04 — до мая 2029 года.

Проверяли на двух контейнерах Docker: «сервер» с systemd и OpenSSH и «ноутбук» только с клиентом SSH. В примерах адрес сервера заменён на документационный 203.0.113.10 — подставьте IP своего VPS. Приглашение # означает команду от root, $ — от обычного пользователя.

/etc

Что там
Настройки этой машины
Пример
/etc/ssh/sshd_config, /etc/systemd/system

/home, /root

Что там
Домашние папки пользователей и root
Пример
/home/deploy/.ssh/authorized_keys

/srv

Что там
Данные, которые сервер отдаёт наружу
Пример
/srv/app — проект из этого урока

/opt

Что там
Сторонние программы, каждая в своей папке
Пример
Агент мониторинга от поставщика

/var/log, /var/lib

Что там
Журналы-файлы и изменяемое состояние программ
Пример
auth.log, dpkg.log; данные баз

/tmp

Что там
Временные файлы, могут удаляться без предупреждения
Пример
Ничего ценного

/usr/bin, /usr/local/bin

Что там
Программы; в local — установленные вручную
Пример
/usr/local/bin/node
terminal
# ls -C /
bin		   etc		      lib64  proc  sbin.usr-is-merged  usr
bin.usr-is-merged  home		      media  root  srv		       var
boot		   lib		      mnt    run   sys
dev		   lib.usr-is-merged  opt    sbin  tmp
# df -h /
Filesystem      Size  Used Avail Use% Mounted on
overlay        1007G  156G  800G  17% /
# du -sh /var/log /usr
26M	/var/log
280M	/usr
# ls -C /var/log
README		  auth.log	 dmesg	   journal   private
alternatives.log  bootstrap.log  dpkg.log  kern.log  syslog
apt		  btmp		 faillog   lastlog   wtmp
Ubuntu 24.04 в контейнере: файловая система overlay и её размер — от Docker, на VPS здесь будет диск вроде /dev/vda1. Каталоги *.usr-is-merged — служебные метки объединения /bin с /usr/bin в Ubuntu 24.04.

SSH: вход по ключу вместо пароля

SSH — зашифрованный канал к серверу. Ключ состоит из двух частей: приватная остаётся на вашем ноутбуке, публичная кладётся на сервер в файл ~/.ssh/authorized_keys. Пароль можно подобрать перебором, подобрать ключ на практике нельзя. Документация Ubuntu рекомендует тип ed25519 — он короче и быстрее RSA; если где-то нужен RSA, берите длину 4096 бит.

terminal
$ ssh-keygen -t ed25519 -C "dev@laptop"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/dev/.ssh/id_ed25519): 
Created directory '/home/dev/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/dev/.ssh/id_ed25519
Your public key has been saved in /home/dev/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:rQv/ITuF0G35TpciyF1zw3hCADi0ObFTTv7dfQ/g2J0 dev@laptop
$ ls -l ~/.ssh
total 16
-rw------- 1 dev dev 444 Oct 12 01:35 id_ed25519
-rw-r--r-- 1 dev dev  92 Oct 12 01:35 id_ed25519.pub
$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICJoFk+NOCBCDZTysLYYWbhJNTTZN/nijzyyyRBFj42/ dev@laptop
На ноутбуке: OpenSSH 9.6p1, реальный вывод; рисунок randomart после отпечатка опущен. Парольная фраза при вводе не отображается.
  • Парольная фраза шифрует приватный ключ на диске: если ноутбук украдут, ключом не воспользуются без неё
  • Файл .pub можно показывать кому угодно — его и вставляют на сервер; приватный id_ed25519 не передают никогда, даже коллегам
  • ssh-keygen сам ставит приватному ключу права 600 — только владелец
  • Отдельный ключ на каждое устройство: потеряли ноутбук — удаляете одну строку из authorized_keys, остальные доступы работают

Как публичный ключ попадает на сервер. У большинства провайдеров VPS при создании сервера есть поле «SSH-ключ» — вставьте туда содержимое id_ed25519.pub, и он окажется в /root/.ssh/authorized_keys. Если провайдер выдал только логин и пароль, используйте ssh-copy-id: команда последний раз войдёт по паролю и допишет ключ. По умолчанию OpenSSH не пускает root по паролю (PermitRootLogin prohibit-password), так что пароль root работает, только если провайдер изменил эту настройку.

terminal
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub ops@203.0.113.10
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/dev/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
ops@203.0.113.10's password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'ops@203.0.113.10'"
and check to make sure that only the key(s) you wanted were added.
Проверено на учётной записи ops с паролем (временное разрешение входа по паролю только для неё, после проверки удалено).

При первом подключении клиент показывает отпечаток ключа сервера и спрашивает, доверять ли ему. После ответа yes отпечаток запоминается в ~/.ssh/known_hosts, и если однажды он изменится, SSH откажется подключаться с громким предупреждением — это защита от подмены сервера.

terminal
$ ssh root@203.0.113.10 'hostname; whoami'
The authenticity of host '203.0.113.10 (203.0.113.10)' can't be established.
ED25519 key fingerprint is SHA256:IaTy+ntmy5vPYW0mWxRtuKucgJ/6xml8wEPCiK2pPoA.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '203.0.113.10' (ED25519) to the list of known hosts.
Enter passphrase for key '/home/dev/.ssh/id_ed25519': 
vps
root
Первый вход с ноутбука, реальный вывод. Имя сервера в тесте — vps.
terminal
# ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
256 SHA256:IaTy+ntmy5vPYW0mWxRtuKucgJ/6xml8wEPCiK2pPoA root@vps (ED25519)
На сервере, через веб-консоль провайдера: тот же отпечаток SHA256, значит, подключились к своему серверу.

Пользователи, группы и sudo

Работать под root опасно: любая опечатка выполняется с полными правами. В Ubuntu пароль root по умолчанию заблокирован, а права администратора выдаются через sudo участникам группы sudo. Поэтому первое действие на новом сервере — создать обычного пользователя, например deploy, и дать ему sudo.

terminal
# adduser deploy
info: Adding user `deploy' ...
info: Selecting UID/GID from range 1000 to 59999 ...
info: Adding new group `deploy' (1000) ...
info: Adding new user `deploy' (1000) with group `deploy (1000)' ...
info: Creating home directory `/home/deploy' ...
info: Copying files from `/etc/skel' ...
New password: 
Retype new password: 
passwd: password updated successfully
Changing the user information for deploy
Enter the new value, or press ENTER for the default
	Full Name []: 
	Room Number []: 
	Work Phone []: 
	Home Phone []: 
	Other []: 
Is the information correct? [Y/n] Y
info: Adding new user `deploy' to supplemental / extra groups `users' ...
info: Adding user `deploy' to group `users' ...
# usermod -aG sudo deploy
# id deploy
uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),100(users)
# ls -ld /home/deploy
drwxr-x--- 2 deploy deploy 4096 Oct 12 01:35 /home/deploy
Ubuntu 24.04, adduser 3.137: на вопросы об имени и телефоне можно просто нажимать Enter. Пароль deploy понадобится для sudo, входить по SSH будем ключом.
  • adduser — дружелюбная обёртка Ubuntu: создаёт группу с тем же именем, домашнюю папку и спрашивает пароль. Низкоуровневый useradd без флагов не создаёт даже домашнюю папку
  • В uid=1000(deploy) gid=1000(deploy) — числовые идентификаторы: ядро проверяет права именно по числам, имена — для людей
  • Флаг -a в usermod -aG обязателен: без него usermod -G users deploy заменит список групп, и пользователь выпадет из sudo (проверено). Вариант из документации Ubuntu — adduser deploy sudo
  • Домашние папки в Ubuntu 21.10 и новее создаются с правами 750: другие пользователи не могут в них заглянуть

Теперь ключ: скопируем authorized_keys от root в домашнюю папку deploy. Типичная ошибка — скопировать от root и забыть про владельца.

terminal
# install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
# cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
# ls -l /home/deploy/.ssh
total 4
-rw------- 1 root root 92 Oct 12 01:35 authorized_keys
install -d создаёт папку сразу с правами и владельцем; а вот cp от root оставил владельцем файла root.
terminal
$ ssh -o BatchMode=yes deploy@203.0.113.10 whoami
deploy@203.0.113.10: Permission denied (publickey,password).

# journalctl -u ssh -n 2 --no-pager
Oct 12 01:36:04 vps sshd[278]: Could not open user 'deploy' authorized keys '/home/deploy/.ssh/authorized_keys': Permission denied
Oct 12 01:36:04 vps sshd[278]: Connection closed by authenticating user deploy 198.51.100.23 port 52416 [preauth]
Сначала ноутбук, затем журнал сервера. BatchMode=yes запрещает клиенту спрашивать пароль, поэтому видно, что ключ не принят.

Ошибка на клиенте ничего не объясняет, а журнал сервера называет причину прямо: sshd проверяет ключи от имени пользователя deploy, а файл принадлежит root и закрыт правами 600. Исправляем владельца:

terminal
# chown deploy:deploy /home/deploy/.ssh/authorized_keys
# chmod 600 /home/deploy/.ssh/authorized_keys
# ls -la /home/deploy/.ssh
total 12
drwx------ 2 deploy deploy 4096 Oct 12 01:35 .
drwxr-x--- 3 deploy deploy 4096 Oct 12 01:35 ..
-rw------- 1 deploy deploy   92 Oct 12 01:35 authorized_keys
После chown вход по ключу проходит.

Чтобы не набирать каждый раз IP и имя пользователя, заведите на ноутбуке файл ~/.ssh/config с псевдонимом, а парольную фразу ключа доверьте ssh-agent — он помнит её до конца сеанса.

~/.ssh/config
Host myvps
    HostName 203.0.113.10
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
Действует первое найденное значение, поэтому конкретные Host пишут выше общих правил. Файл должен быть закрыт от записи другими — ставим 600.
terminal
$ eval "$(ssh-agent -s)"
Agent pid 363
$ ssh-add
Enter passphrase for /home/dev/.ssh/id_ed25519: 
Identity added: /home/dev/.ssh/id_ed25519 (dev@laptop)
$ chmod 600 ~/.ssh/config
$ ssh myvps 'whoami; id'
deploy
uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),100(users)
$ ssh -t myvps 'sudo whoami; sudo -l'
[sudo] password for deploy: 
root
Matching Defaults entries for deploy on vps:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User deploy may run the following commands on vps:
    (ALL : ALL) ALL
Connection to 203.0.113.10 closed.
Ноутбук, реальный вывод. sudo спрашивает пароль самого deploy, а не root, и запоминает его на некоторое время.

Владельцы и права на сервере: chown, umask, namei

Как читать rwxr-x--- и что значат chmod 644 и chmod 600, разобрано в уроке о терминале. На сервере добавляется вопрос «кто»: у каждого файла есть владелец и группа, а у каждого процесса — пользователь, от имени которого он работает. Право проверяется у тройки, которая подходит процессу: владелец, иначе группа, иначе все остальные. Подготовим папку проекта: создадим её через sudo и отдадим deploy.

terminal
$ sudo mkdir -p /srv/app/public
$ ls -ld /srv/app
drwxr-xr-x 3 root root 4096 Oct 12 01:37 /srv/app
$ sudo chown -R deploy:deploy /srv/app
$ ls -ld /srv/app /srv/app/public
drwxr-xr-x 3 deploy deploy 4096 Oct 12 01:37 /srv/app
drwxr-xr-x 2 deploy deploy 4096 Oct 12 01:37 /srv/app/public
$ umask
0002
$ echo '<h1>Hello from /srv/app</h1>' > /srv/app/public/index.html
$ printf 'PORT=8080\nAPP_SECRET=change-me\n' > /srv/app/.env
$ ls -la /srv/app /srv/app/public
/srv/app:
total 16
drwxr-xr-x 3 deploy deploy 4096 Oct 12 01:37 .
drwxr-xr-x 1 root   root   4096 Oct 12 01:37 ..
-rw-rw-r-- 1 deploy deploy   31 Oct 12 01:37 .env
drwxr-xr-x 2 deploy deploy 4096 Oct 12 01:37 public

/srv/app/public:
total 12
drwxr-xr-x 2 deploy deploy 4096 Oct 12 01:37 .
drwxr-xr-x 3 deploy deploy 4096 Oct 12 01:37 ..
-rw-rw-r-- 1 deploy deploy   29 Oct 12 01:37 index.html
$ chmod 600 /srv/app/.env
$ chmod 750 /srv/app
$ stat -c '%A %a %U:%G %n' /srv/app /srv/app/.env /srv/app/public/index.html
drwxr-x--- 750 deploy:deploy /srv/app
-rw------- 600 deploy:deploy /srv/app/.env
-rw-rw-r-- 664 deploy:deploy /srv/app/public/index.html
$ sudo adduser --disabled-password --comment "" intern > /dev/null
$ sudo -u intern cat /srv/app/public/index.html
cat: /srv/app/public/index.html: Permission denied
$ sudo -u intern ls /srv/app
ls: cannot open directory '/srv/app': Permission denied
$ namei -l /srv/app/public/index.html
f: /srv/app/public/index.html
drwxr-xr-x root   root   /
drwxr-xr-x root   root   srv
drwxr-x--- deploy deploy app
drwxr-xr-x deploy deploy public
-rw-rw-r-- deploy deploy index.html
Сеанс deploy по SSH, реальный вывод. Пользователь intern создан только для проверки доступа.
  • sudo mkdir создал папку от root — писать в неё deploy не мог бы. chown -R deploy:deploy передал владельца и группу рекурсивно
  • umask 0002 — маска прав для новых файлов: из 666 вычитается 002, получается 664 (rw-rw-r--). Поэтому .env после создания был доступен на чтение всем, и права закрыли явно — chmod 600
  • Право x у папки означает «можно войти». У /srv/app с правами 750 посторонние не войдут, и файл index.html с правами 664 внутри им тоже недоступен
  • namei -l показывает права на каждом шаге пути — сразу видно, на каком каталоге доступ обрывается

~/.ssh

Права и владелец
700, владелец — пользователь
Почему
sshd отвергает ключи, если папку могут менять другие

~/.ssh/authorized_keys

Права и владелец
600, владелец — пользователь
Почему
Читается от имени пользователя, писать — только ему

/srv/app

Права и владелец
750, deploy:deploy
Почему
Приложению — всё, группе — чтение, остальным — ничего

/srv/app/.env

Права и владелец
600, владелец — пользователь сервиса
Почему
Пароли и ключи видит только приложение

/etc/systemd/system/*.service

Права и владелец
644, root:root
Почему
Описание сервиса правит только администратор

Процессы, порты и curl

Процесс — запущенная программа с номером PID и пользователем-владельцем. Запустим в фоне простой веб-сервер Python из папки проекта и посмотрим на него всеми основными инструментами: pgrep ищет процессы по имени, ps даёт снимок списка, top — живую таблицу нагрузки (выход — q).

terminal
$ nohup python3 -m http.server 8080 --bind 127.0.0.1 --directory /srv/app/public > /tmp/http.log 2>&1 &
$ sleep 1; pgrep -a python3
2241 python3 -m http.server 8080 --bind 127.0.0.1 --directory /srv/app/public
$ ps -eo pid,user,%cpu,%mem,cmd --sort=-%mem | head -5
    PID USER     %CPU %MEM CMD
   2241 deploy    0.9  0.0 python3 -m http.server 8080 --bind 127.0.0.1 --directory /srv/app/public
     24 root      0.0  0.0 /usr/lib/systemd/systemd-journald
      1 root      0.0  0.0 /sbin/init
   2225 root      0.0  0.0 sshd: deploy [priv]
$ top -b -n 1 | head -8
top - 01:47:17 up 3 days, 11:53,  0 user,  load average: 2.15, 3.11, 3.34
Tasks:  20 total,   1 running,  19 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.3 sy,  0.0 ni, 97.9 id,  0.0 wa,  0.0 hi,  1.7 si,  0.0 st 
MiB Mem :  31936.7 total,   4438.6 free,   4555.4 used,  23685.6 buff/cache     
MiB Swap:   8192.0 total,   8190.3 free,      1.7 used.  27381.2 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
      1 root      20   0   21336  12752   9684 S   0.0   0.0   0:00.63 systemd
$ sudo ss -tlnp
State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess                                                
LISTEN 0      4096         0.0.0.0:22         0.0.0.0:*    users:(("sshd",pid=2066,fd=3),("systemd",pid=1,fd=93))
LISTEN 0      4096      127.0.0.11:41039      0.0.0.0:*                                                          
LISTEN 0      5          127.0.0.1:8080       0.0.0.0:*    users:(("python3",pid=2241,fd=3))                     
LISTEN 0      4096            [::]:22            [::]:*    users:(("sshd",pid=2066,fd=4),("systemd",pid=1,fd=94))
$ curl -sS http://127.0.0.1:8080/
<h1>Hello from /srv/app</h1>
$ curl -sI http://127.0.0.1:8080/
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.3
Date: Mon, 12 Oct 2026 01:47:17 GMT
Content-type: text/html
Content-Length: 29
Last-Modified: Mon, 12 Oct 2026 01:37:06 GMT

$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/missing
404
$ curl -sS http://127.0.0.1:8081/
curl: (7) Failed to connect to 127.0.0.1 port 8081 after 0 ms: Couldn't connect to server
$ kill $(pgrep -f 'http[.]server 8080')
$ sleep 1; pgrep -a python3 || echo "no python3 processes"
no python3 processes
$ cat /tmp/http.log
127.0.0.1 - - [12/Oct/2026 01:47:17] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [12/Oct/2026 01:47:17] "HEAD / HTTP/1.1" 200 -
127.0.0.1 - - [12/Oct/2026 01:47:17] code 404, message File not found
127.0.0.1 - - [12/Oct/2026 01:47:17] "GET /missing HTTP/1.1" 404 -
Сеанс deploy, реальный вывод. Строка 127.0.0.11:41039 — внутренний DNS Docker, на VPS её не будет; нагрузка и память в top — хоста, на котором шёл тест.

ss -tlnp — главный ответ на вопрос «кто слушает порт»: t — TCP, l — только слушающие сокеты, n — числа вместо имён, p — процесс. Без sudo процессы других пользователей не показываются. Обратите внимание на адрес: 127.0.0.1:8080 доступен только с самого сервера, 0.0.0.0:22 — со всех интерфейсов. Порт 22 в Ubuntu 24.04 держит сам systemd (юнит ssh.socket) и запускает sshd при подключении — это видно в колонке процессов.

curl -sS URL

Что показывает
Тело ответа; -s убирает индикатор, -S оставляет сообщения об ошибках

curl -sI URL

Что показывает
Только заголовки ответа: код, тип, длина

curl -s -o /dev/null -w '%{http_code}\n' URL

Что показывает
Только код ответа — удобно в скриптах проверки

curl: (7) Failed to connect

Что показывает
На порту никто не слушает: процесс не запущен или слушает другой адрес

SIGTERM (15)

Как отправить
kill PID — по умолчанию
Что происходит
Просьба завершиться: программа может закрыть соединения и сохранить данные

SIGINT (2)

Как отправить
Ctrl+C в терминале
Что происходит
Прерывание из терминала, обычно ведёт себя как SIGTERM

SIGHUP (1)

Как отправить
kill -HUP PID
Что происходит
Многие демоны по нему перечитывают конфигурацию

SIGKILL (9)

Как отправить
kill -9 PID
Что происходит
Немедленное уничтожение ядром, перехватить нельзя — последнее средство

Процесс, запущенный через nohup … &, переживёт выход из SSH, но не перезагрузку, и если он упадёт, никто его не поднимет. Для приложения на сервере нужен менеджер сервисов — systemd.

systemd: приложение как сервис и журналы

systemd — первый процесс системы (PID 1): он запускает службы при загрузке, перезапускает упавшие и собирает их вывод в журнал. Службу описывает unit-файл. Свои юниты кладут в /etc/systemd/system. Опишем наш веб-сервер — порт он возьмёт из .env.

/etc/systemd/system/hello-web.service
[Unit]
Description=Hello web: static files from /srv/app/public
After=network.target

[Service]
Type=exec
User=deploy
Group=deploy
WorkingDirectory=/srv/app
EnvironmentFile=/srv/app/.env
ExecStart=/usr/bin/python3 -m http.server ${PORT} --bind 127.0.0.1 --directory /srv/app/public
Restart=on-failure
RestartSec=2

[Install]
WantedBy=multi-user.target
Вместо Python в реальном проекте будет ExecStart=/usr/local/bin/node server.mjs — такой юнит для Node.js собран в упражнении ниже. Создайте файл через sudo nano.

User=, Group=

Зачем
Процесс работает от deploy, а не от root

EnvironmentFile=

Зачем
Переменные из .env; файл читает systemd, поэтому права 600 у deploy ему не мешают

ExecStart=

Зачем
Команда запуска с абсолютным путём; ${PORT} подставляется из окружения

Restart=on-failure, RestartSec=2

Зачем
Перезапуск через 2 секунды, если процесс упал

WantedBy=multi-user.target

Зачем
Что включает systemctl enable: запуск при обычной загрузке
terminal
$ sudo cp /tmp/hello-web.service /etc/systemd/system/hello-web.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now hello-web
Created symlink /etc/systemd/system/multi-user.target.wants/hello-web.service → /etc/systemd/system/hello-web.service.
$ systemctl status hello-web --no-pager
● hello-web.service - Hello web: static files from /srv/app/public
     Loaded: loaded (/etc/systemd/system/hello-web.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-10-12 01:47:42 UTC; 5ms ago
   Main PID: 2384 (python3)
      Tasks: 1 (limit: 38311)
     Memory: 2.4M (peak: 2.4M)
        CPU: 3ms
     CGroup: /system.slice/hello-web.service
             └─2384 /usr/bin/python3 -m http.server 8080 --bind 127.0.0.1 --dir…
$ curl -sS http://127.0.0.1:8080/
curl: (7) Failed to connect to 127.0.0.1 port 8080 after 0 ms: Couldn't connect to server
$ sleep 1; curl -sS http://127.0.0.1:8080/
<h1>Hello from /srv/app</h1>
$ journalctl -u hello-web -n 5 --no-pager
Hint: You are currently not seeing messages from other users and the system.
      Users in groups 'adm', 'systemd-journal' can see all messages.
      Pass -q to turn off this notice.
Oct 12 01:47:43 vps python3[2384]: 127.0.0.1 - - [12/Oct/2026 01:47:43] "GET / HTTP/1.1" 200 -
$ sudo journalctl -u hello-web -n 5 --no-pager
Oct 12 01:47:42 vps systemd[1]: Starting hello-web.service - Hello web: static files from /srv/app/public...
Oct 12 01:47:42 vps systemd[1]: Started hello-web.service - Hello web: static files from /srv/app/public.
Oct 12 01:47:43 vps python3[2384]: 127.0.0.1 - - [12/Oct/2026 01:47:43] "GET / HTTP/1.1" 200 -
$ sudo kill -9 $(systemctl show -p MainPID --value hello-web)
$ sleep 3; systemctl status hello-web --no-pager | head -5
● hello-web.service - Hello web: static files from /srv/app/public
     Loaded: loaded (/etc/systemd/system/hello-web.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-10-12 01:47:46 UTC; 815ms ago
   Main PID: 2401 (python3)
      Tasks: 1 (limit: 38311)
$ sudo journalctl -u hello-web -n 6 --no-pager
Oct 12 01:47:43 vps python3[2384]: 127.0.0.1 - - [12/Oct/2026 01:47:43] "GET / HTTP/1.1" 200 -
Oct 12 01:47:43 vps systemd[1]: hello-web.service: Main process exited, code=killed, status=9/KILL
Oct 12 01:47:43 vps systemd[1]: hello-web.service: Failed with result 'signal'.
Oct 12 01:47:46 vps systemd[1]: hello-web.service: Scheduled restart job, restart counter is at 1.
Oct 12 01:47:46 vps systemd[1]: Starting hello-web.service - Hello web: static files from /srv/app/public...
Oct 12 01:47:46 vps systemd[1]: Started hello-web.service - Hello web: static files from /srv/app/public.
$ sudo kill $(systemctl show -p MainPID --value hello-web)
$ sleep 3; systemctl is-active hello-web; systemctl is-enabled hello-web
inactive
enabled
$ sudo systemctl start hello-web
$ systemctl is-active hello-web
active
$ sudo journalctl -u hello-web --since "10 min ago" -p warning --no-pager
Oct 12 01:47:43 vps systemd[1]: hello-web.service: Main process exited, code=killed, status=9/KILL
Oct 12 01:47:43 vps systemd[1]: hello-web.service: Failed with result 'signal'.
$ systemctl list-units --type=service --state=failed --no-pager
  UNIT LOAD ACTIVE SUB DESCRIPTION

0 loaded units listed.
Сеанс deploy, systemd 255, реальный вывод (цвета убраны).

Разберём, что произошло. Первый curl сразу после старта не дозвался: при Type=exec systemd считает службу запущенной, как только программа стартовала, а не когда она открыла порт. Проверки после деплоя поэтому повторяют запрос несколько раз или ждут ответа health-эндпоинта. kill -9 служба пережила: Restart=on-failure перезапускает процесс, завершённый сигналом. А обычный kill (SIGTERM) systemd считает чистым завершением — по документации systemd.service сигналы SIGHUP, SIGINT, SIGTERM и SIGPIPE не считаются сбоем, поэтому служба осталась остановленной. Если нужно поднимать её в любом случае, есть Restart=always.

-u hello-web

Что делает
Только записи этого юнита

-f

Что делает
Показывать новые записи по мере появления, как tail -f

-n 50

Что делает
Последние 50 строк (без числа — 10)

--since "10 min ago", --since today

Что делает
Записи за период

-p warning

Что делает
Только предупреждения и серьёзнее

-b

Что делает
Только с последней загрузки

Пакеты, файрвол и вход только по ключу

Программы в Ubuntu ставят из репозиториев через apt. apt update только обновляет списки пакетов, apt upgrade устанавливает новые версии — поэтому сначала первое, потом второе. apt рассчитан на человека; в скриптах документация Ubuntu советует apt-get. Источники пакетов в 24.04 описаны в /etc/apt/sources.list.d/ubuntu.sources, а по умолчанию Ubuntu Server сам ставит обновления безопасности.

terminal
$ sudo apt update
Hit:1 http://security.ubuntu.com/ubuntu noble-security InRelease
Hit:2 http://archive.ubuntu.com/ubuntu noble InRelease
Hit:3 http://archive.ubuntu.com/ubuntu noble-updates InRelease
Hit:4 http://archive.ubuntu.com/ubuntu noble-backports InRelease
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
2 packages can be upgraded. Run 'apt list --upgradable' to see them.
$ apt list --upgradable
Listing... Done
libaudit-common/noble-updates 1:3.1.2-2.1ubuntu0.1 all [upgradable from: 1:3.1.2-2.1build1.1]
libaudit1/noble-updates 1:3.1.2-2.1ubuntu0.1 amd64 [upgradable from: 1:3.1.2-2.1build1.1]
$ sudo apt install -y tree ufw
…
The following NEW packages will be installed:
  iptables libip4tc2 libip6tc2 libjansson4 libnetfilter-conntrack3
  libnfnetlink0 libnftables1 libnftnl11 nftables tree ufw
0 upgraded, 11 newly installed, 0 to remove and 2 not upgraded.
…
Setting up tree (2.1.1-2ubuntu3.24.04.2) ...
Setting up ufw (0.36.2-6) ...
…
$ tree -a /srv/app
/srv/app
├── .env
└── public
    └── index.html

2 directories, 2 files
$ apt policy tree
tree:
  Installed: 2.1.1-2ubuntu3.24.04.2
  Candidate: 2.1.1-2ubuntu3.24.04.2
  Version table:
 *** 2.1.1-2ubuntu3.24.04.2 500
        500 http://archive.ubuntu.com/ubuntu noble-updates/universe amd64 Packages
        100 /var/lib/dpkg/status
     2.1.1-2ubuntu3 500
        500 http://archive.ubuntu.com/ubuntu noble/universe amd64 Packages
$ sudo apt remove -y tree
…
The following packages will be REMOVED:
  tree
…
Removing tree (2.1.1-2ubuntu3.24.04.2) ...
$ grep -E " (install|remove) tree" /var/log/dpkg.log
2026-10-12 01:40:02 install tree:amd64 <none> 2.1.1-2ubuntu3.24.04.2
2026-10-12 01:40:04 remove tree:amd64 2.1.1-2ubuntu3.24.04.2 <none>
Реальный вывод, длинные фрагменты установки сокращены (…). Каждая установка и удаление записываются в /var/log/dpkg.log.

Файрвол ufw в Ubuntu изначально выключен. Порядок важен: сначала разрешить SSH, потом включить — иначе новое подключение к серверу станет невозможным. Профиль OpenSSH открывает порт 22/tcp. Остальные входящие подключения после включения отклоняются: в нашей проверке тестовый сервер на 0.0.0.0:8090 перестал отвечать снаружи (тайм-аут curl).

terminal
$ sudo ufw allow OpenSSH
Rules updated
Rules updated (v6)
$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           ALLOW IN    Anywhere                  
22/tcp (OpenSSH (v6))      ALLOW IN    Anywhere (v6)             
ufw 0.36.2, реальный вывод; правило добавлено до включения файрвола.

Последний шаг — запретить вход по паролю и вход root. Настройки сервера SSH читаются из /etc/ssh/sshd_config, а он в Ubuntu первым делом подключает файлы из /etc/ssh/sshd_config.d/. Для sshd действует первое найденное значение, поэтому свой файл называем с маленьким номером — он прочитается раньше файлов, которые могли положить провайдер или cloud-init.

/etc/ssh/sshd_config.d/01-hardening.conf
# Key-only login; root cannot log in over SSH
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
Права файла — 644, владелец root.
terminal
$ sudo nano /etc/ssh/sshd_config.d/01-hardening.conf
$ echo 'PasswordAuthentification no' | sudo tee /etc/ssh/sshd_config.d/02-typo.conf
PasswordAuthentification no
$ sudo sshd -t
/etc/ssh/sshd_config.d/02-typo.conf: line 1: Bad configuration option: PasswordAuthentification
/etc/ssh/sshd_config.d/02-typo.conf: terminating, 1 bad configuration options
$ sudo rm /etc/ssh/sshd_config.d/02-typo.conf
$ sudo sshd -t && echo "config OK"
config OK
$ sudo sshd -T | grep -E '^(passwordauthentication|permitrootlogin|kbdinteractiveauthentication|pubkeyauthentication) '
permitrootlogin no
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no
$ sudo systemctl restart ssh.service
$ systemctl is-active ssh.socket ssh.service
active
active
Сеанс deploy, реальный вывод. Опечатка добавлена специально, чтобы показать, как sshd -t ловит ошибку до перезапуска.
terminal
$ ssh -o PubkeyAuthentication=no myvps
deploy@203.0.113.10: Permission denied (publickey).
$ ssh root@203.0.113.10
root@203.0.113.10: Permission denied (publickey).
$ ssh myvps hostname
vps
Проверка с ноутбука в новом окне: пароль больше не принимается, root не входит даже с ключом, вход deploy по ключу работает.

sshd -T печатает итоговые значения настроек — так проверяют, что файл действительно применился. В Ubuntu 24.04 порт 22 держит ssh.socket, поэтому смену порта ищите в документации именно для этой версии: с socket-активацией порядок действий отличается от инструкций для старых выпусков.

Чек-лист первичной настройки и упражнения

  • Ключ ed25519 с парольной фразой, отпечаток сервера сверен с консолью провайдера
  • Создан пользователь с sudo, вход под ним по ключу проверен в новом окне
  • В sshd_config.d: PasswordAuthentication no, PermitRootLogin no; sudo sshd -t без ошибок
  • ufw: разрешён OpenSSH, затем включён; sudo ufw status verbose показывает правила
  • Выполнены sudo apt update и sudo apt upgrade
  • Проект в /srv/app, владелец — пользователь сервиса, .env с правами 600
  • Приложение работает как сервис systemd с Restart=on-failure, включено через enable и слушает 127.0.0.1
  • Известно, где смотреть: journalctl -u <сервис>, /var/log/auth.log, ss -tlnp

Следующий шаг — Docker: тот же сервер, но приложение упаковано в образ со всеми зависимостями и запускается одной командой. Навыки этого урока — пользователи, права, порты и журналы — понадобятся и внутри контейнеров.

Как проверено14 · пройденосверено 11 октября 2026 г.
  • запущено, работает

    файловая система: ls -C /, df -h /, du -sh, ls -C /var/log

    контейнер kb-linux-vps (ubuntu:24.04 = 24.04.5 + systemd 255 + openssh-server 9.6p1, --privileged, cgroupns private), реальный вывод

  • запущено, работает

    ssh-keygen ed25519 с парольной фразой, ssh-agent/ssh-add, первый вход с проверкой отпечатка, ssh-keygen -lf на сервере, ~/.ssh/config (Host myvps)

    контейнер kb-linux-laptop (ubuntu:24.04 + openssh-client) → kb-linux-vps в сети kb-linux-net; интерактивные запросы через script(1) с подачей ответов; отпечатки совпали

  • запущено, работает

    ssh-copy-id по паролю

    учётная запись ops с паролем и временным Match User ops / PasswordAuthentication yes (удалено после проверки): Number of key(s) added: 1, вход новым ключом. В проверке копировался второй ключ id_ed25519_second.pub, в статье имя файла заменено на id_ed25519.pub, хост — на 203.0.113.10

  • запущено, работает

    adduser deploy, usermod -aG sudo, id, ls -ld /home/deploy (750), sudo whoami / sudo -l

    adduser 3.137ubuntu1 в pty через script; вывод вопросов adduser собран из того же прогона (ответы — Enter); usermod -G без -a выкидывает из sudo — проверено в отдельном ubuntu:24.04

  • запущено, работает

    ошибка владельца authorized_keys (root:root 600) и исправление chown

    клиент: Permission denied (publickey,password) при BatchMode=yes; journalctl -u ssh: Could not open user 'deploy' authorized keys … Permission denied; после chown вход проходит

  • запущено, работает

    права: mkdir/chown -R, umask 0002, chmod 600/.env, chmod 750, stat, отказ intern, namei -l

    сеанс deploy по SSH с sudo (пароль введён через pty), реальный вывод

  • запущено, работает

    процессы и сеть: nohup python3 -m http.server, pgrep, ps, top -b, sudo ss -tlnp, curl -sS/-sI/-w http_code, curl (7), kill

    сеанс deploy, Python 3.12.3; строка 127.0.0.11 — DNS Docker, оговорена в подписи

  • запущено, работает

    hello-web.service: daemon-reload, enable --now, status, гонка Type=exec, journalctl (без sudo и с sudo), kill -9 → перезапуск, kill (SIGTERM) → inactive, -p warning, list-units --failed

    systemd 255.4 в контейнере; журнал очищен (rotate+vacuum) перед прогоном; ANSI-цвета удалены из вывода. В исходном образе не было dbus — systemctl от обычного пользователя падал с Failed to connect to bus; установлен dbus (на Ubuntu Server он есть)

  • запущено, работает

    apt update, apt list --upgradable, apt install tree ufw, tree, apt policy, apt remove, dpkg.log

    реальные репозитории noble; в статье вывод очищен от строк прогресса и сокращён (…)

  • запущено, работает

    ufw allow OpenSSH, ufw enable, status verbose

    ufw 0.36.2 (iptables-nft) в привилегированном контейнере; SSH работает после включения; порт 8090 на 0.0.0.0 снаружи — тайм-аут curl (28). Правило allow и enable — из двух последовательных прогонов на одном сервере

  • запущено, работает

    01-hardening.conf, sshd -t (с опечаткой и без), sshd -T, systemctl restart ssh.service, проверка входа

    OpenSSH 9.6p1: Bad configuration option ловится sshd -t; после перезапуска пароль и root отклоняются (Permission denied (publickey)), вход deploy по ключу работает; в статье первая строка «sudo nano …» вместо фактического «sudo cp» готового файла

  • запущено, работает

    упражнение repeat (права и группы)

    docker run --rm ubuntu:24.04 (kb-linux-ex1): Permission denied до adduser intern deploy, чтение после

  • запущено, работает

    упражнение modify (порт через .env)

    PORT=8081 в .env + systemctl restart без daemon-reload: ss показал 127.0.0.1:8081, curl ответил

  • запущено, работает

    упражнение apply (server.mjs + api.service скриптом)

    скрипт выполнен целиком от deploy (bash -x): Node.js 24.18.0 (бинарник из node:24.18.0-bookworm-slim в /usr/local/bin/node), /health → {"status":"ok","env":"production"}, процесс от deploy, при stop — SIGTERM received, closing server

«Запущено» — код выполнен во временном проекте или контейнере, вывод в статье взят из прогона. «Частично» — проверено с заглушками вместо внешних сервисов. «Не запускалось» — нужны устройство, аккаунт или ключ; шаги сверены с официальной документацией. Файлы проверочного проекта и логи запусков сохранены в архиве InterLAB.

Расскажите о задаче — мы изучим запрос и свяжемся с вами

Есть задача?
Обсудим и оценим — бесплатно.

Расскажите о продукте или проблеме. Предложим архитектуру, срок и стоимость первой версии после изучения задачи.

  • Оценка объёма и рисков до старта
  • NDA — по вашему или нашему шаблону
  • Старт работ в течение недели

Не присылайте через форму пароли, ключи доступа и исходный код — безопасную передачу доступа обсудим отдельно.

Нажимая кнопку, вы соглашаетесь с обработкой персональных данных.