
Creeaza un serviciu systemd ca aplicatia ta sa porneasca la boot
Ai scris o aplicatie, te-ai conectat prin SSH pe VPS, ai tastat node server.js si a mers. Apoi ai inchis laptopul, sesiunea SSH s-a terminat si aplicatia a murit odata cu ea. Poate ai fost mai istet si ai pornit-o in screen sau cu nohup, si a mers trei saptamani, pana la primul update de kernel si primul reboot. Dupa aceea n-a observat nimeni ca a disparut, pana a sunat un client. Exista o cale mai buna, si e deja instalata pe server: systemd. In tutorialul asta cream un serviciu systemd care porneste aplicatia la boot, o reporneste cand cade, o ruleaza cu un user fara privilegii, tine secretele intr-un fisier separat, o inchide intr-un sandbox si trimite fiecare linie de log intr-un loc in care chiar poti cauta.
Folosim ca exemplu o aplicatie Node.js foarte mica, apoi aratam exact ce se schimba pentru Python si Go. Totul e scris pentru Debian 13 si Ubuntu 24.04 pe un VPS proaspat, iar acolo unde cele doua distributii se comporta diferit, spunem explicit.
Ce este systemd si de ce sa-l folosesti pentru aplicatia ta
Cand Linux porneste, kernelul lanseaza singur un singur program: procesul numarul 1, adica PID 1. Pe Debian si Ubuntu, programul asta e systemd. Tot restul (SSH, cron, serverul web, baza de date) e pornit de systemd, supravegheat de systemd si oprit de systemd la shutdown. E un “service manager”, un supraveghetor care tine programele de lunga durata, numite servicii sau daemoni, in starea pe care i-ai cerut-o.
Cativa termeni pe care o sa-i folosim des:
- Unit: orice lucru gestionat de systemd. Un serviciu e un tip de unit; mai exista timere, mount-uri, socket-uri si target-uri.
- Fisier unit: un fisier text mic, in format INI (sectiuni intre paranteze drepte, linii
Cheie=valoare), care descrie un unit. Al nostru se va numimyapp.service. - Target: un grup de unit-uri cu nume, care marcheaza o etapa a boot-ului.
multi-user.targetinseamna “sistemul e pornit, reteaua si serviciile ruleaza, utilizatorii se pot loga”. Legandu-ne serviciul de el, il facem sa porneasca la boot. - Journal: depozitul central de loguri al lui systemd, gestionat de serviciul
systemd-journaldsi citit cu comandajournalctl.
De ce nu screen, nohup sau un process manager?
screen, tmux si nohup tin un proces in viata dupa ce te deloghezi, si atat. Nu-l pornesc dupa reboot, nu-l repornesc dupa un crash si lasa logurile imprastiate prin fisiere aleatorii. Process manager-ele specifice unui limbaj, cum e PM2, rezolva mai mult, dar sunt inca un daemon de instalat, de actualizat si de pornit la boot, si acopera un singur limbaj. systemd ruleaza deja pe orice server Debian sau Ubuntu, merge la fel pentru Node.js, Python, Go, Java sau un script shell, si are optiuni de sandboxing pe care niciun process manager nu le are. Daca aplicatia ta e deja un container Docker, politicile de restart din Docker acopera cam acelasi lucru; pentru un binar sau un script, serviciul systemd e varianta mai usoara.
Ce iti trebuie
- Un VPS cu Debian 13 sau Ubuntu 24.04. Un VPS KVM suporta tot ce e in ghid; intr-un container LXC cateva optiuni de sandboxing pot avea nevoie de ajustari (le semnalam la Pasul 8).
- Acces SSH cu un user obisnuit care are drepturi
sudo, nu cu root. - O aplicatie care ruleaza in foreground si asculta pe un port, sau exemplul nostru de 10 linii.
- Un editor de text; noi folosim
nano. - Cam 20 de minute.
Cum se leaga piesele intre ele
La boot, sau ori de cate ori rulezi systemctl start, systemd citeste doua fisiere pe care le cream imediat: fisierul unit, care spune cum se ruleaza aplicatia, si fisierul de mediu, care tine configuratia si secretele. Apoi porneste aplicatia cu un user dedicat, intr-un sandbox in care aproape tot sistemul de fisiere e doar pentru citire. Tot ce afiseaza aplicatia pe iesirea standard sau pe iesirea de erori ajunge direct in journal, deci nu mai ai fisiere de log de rotit. Daca procesul cade, systemd asteapta cateva secunde si il porneste din nou.
Aplicatia asculta doar pe 127.0.0.1 (interfata loopback, accesibila numai de pe server), deci nu e expusa in internet. Daca trebuie sa fie publica, in fata ei pui un reverse proxy, de exemplu Nginx cu certificat TLS.
Cum creezi un serviciu systemd, pas cu pas
Pasul 1: Scrie si testeaza o aplicatie mica
Mai intai avem nevoie de Node.js. Pachetele din distributie sunt suficiente pentru exemplu:
sudo apt update
sudo apt install -y nodejs curl
node --version
command -v node
Debian 13 vine cu Node.js 20, iar Ubuntu 24.04 cu Node.js 18 (deja iesit din suport). Ambele ruleaza exemplul fara probleme, dar pentru o aplicatie reala foloseste o versiune LTS actuala, 22 sau mai noua, din descarcarile oficiale de pe nodejs.org
, si verifica pagina de release-uri Node.js pentru cea mai noua. Ultima comanda afiseaza unde se afla binarul node, de obicei /usr/bin/node. Noteaza calea: systemd are nevoie de cai absolute.
Acum cream o aplicatie web minuscula. Nu are dependinte si isi citeste portul si mesajul din variabile de mediu, exact cum o vom configura mai tarziu:
mkdir -p ~/myapp
nano ~/myapp/server.js
Lipeste codul de mai jos, salveaza cu Ctrl+O, Enter si iesi cu Ctrl+X:
const http = require('http');
const port = Number(process.env.PORT || 3000);
const greeting = process.env.GREETING || 'Hello';
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(`${greeting} from myapp, pid ${process.pid}\n`);
}).listen(port, '127.0.0.1', () => {
console.log(`listening on 127.0.0.1:${port}`);
});
Rul-o manual, ca sa fii sigur ca merge inainte sa intre systemd in joc:
PORT=3000 GREETING=Salut node ~/myapp/server.js
Ar trebui sa vezi listening on 127.0.0.1:3000. Deschide o a doua sesiune SSH si ruleaza curl http://127.0.0.1:3000; raspunsul e Salut from myapp, pid 12345 (cu alt numar). Intoarce-te in prima sesiune si opreste aplicatia cu Ctrl+C. Testul manual conteaza: daca aplicatia nu merge pornita de mana, nu va merge nici ca serviciu, iar depanarea e mult mai simpla aici.
Pasul 2: Creeaza un user de sistem dedicat
Daca rulezi o aplicatie ca root, orice bug din ea, sau din vreuna din sutele ei de dependinte, poate prelua controlul intregului server. In schimb, cream un user de sistem: un cont fara parola, fara shell de login si fara director home, al carui unic rol e sa detina procesul care ruleaza.
sudo useradd --system --user-group --home-dir /opt/myapp --no-create-home --shell /usr/sbin/nologin myapp
id myapp
Iesirea arata ca uid=998(myapp) gid=998(myapp) groups=998(myapp); numerele difera. --system ii da contului un ID mic, rezervat serviciilor, --user-group creeaza grupul myapp corespunzator, iar /usr/sbin/nologin garanteaza ca nimeni nu se poate loga cu userul asta, atat pe Debian cat si pe Ubuntu.
Pasul 3: Instaleaza aplicatia in /opt
Directorul tau home e pentru tine, nu pentru servicii. Copiem aplicatia in /opt/myapp si o facem detinuta de root, dar citibila de grupul myapp. Asa, serviciul isi poate citi codul, dar nu il poate modifica, deci o aplicatie compromisa nu-si poate planta singura un backdoor.
sudo mkdir -p /opt/myapp
sudo cp -r ~/myapp/. /opt/myapp/
sudo chown -R root:myapp /opt/myapp
sudo chmod -R u=rwX,g=rX,o= /opt/myapp
ls -l /opt/myapp
Te astepti la o linie de genul -rw-r----- 1 root myapp 352 Nov 10 10:02 server.js. In u=rwX,g=rX,o=, X mare adauga dreptul de executie doar directoarelor si fisierelor care erau deja executabile, iar o= taie orice acces pentru ceilalti utilizatori. Pentru un proiect Node.js real, ruleaza npm ci --omit=dev in folderul de build (~/myapp) inainte de copiere, ca sa vina si node_modules.
Pasul 4: Creeaza fisierul de mediu
Configuratia si secretele nu au ce cauta in cod sau in fisierul unit. Le punem intr-un fisier separat pe care systemd il incarca atunci cand porneste serviciul:
sudo mkdir -p /etc/myapp
sudo nano /etc/myapp/myapp.env
# /etc/myapp/myapp.env
PORT=3000
GREETING=Salut
NODE_ENV=production
# Exemplu de secret, inlocuieste-l cu o valoare lunga si aleatorie
API_TOKEN=schimba-ma-cu-un-sir-lung-si-aleatoriu
Apoi il restrictionam, ca doar root sa-l poata citi:
sudo chown root:root /etc/myapp/myapp.env
sudo chmod 600 /etc/myapp/myapp.env
Poate te intrebi cum citeste aplicatia un fisier pe care nu are voie sa-l deschida. Nu trebuie sa-l citeasca: systemd insusi (care ruleaza ca root) citeste fisierul inainte sa treaca pe userul myapp si ii da procesului valorile sub forma de variabile de mediu. Formatul e strict, pentru ca systemd nu e un shell: un NUME=valoare pe linie, fara spatii in jurul lui =, fara cuvantul export, fara substitutii de tip $(comanda). Liniile care incep cu # sunt comentarii, iar valorile cu spatii se pun intre ghilimele duble.
Pasul 5: Scrie fisierul unit
Fisierele unit scrise de tine stau in /etc/systemd/system/. Cele instalate de pachete stau in /usr/lib/systemd/system/ (accesibil si ca /lib/systemd/system/ pe ambele distributii); nu-ti pune niciodata fisierele acolo, pentru ca directorul ala apartine update-urilor de pachete.
sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=My example web app
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Type=exec
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/myapp.env
ExecStart=/usr/bin/node /opt/myapp/server.js
Restart=on-failure
RestartSec=5
TimeoutStopSec=20
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
Ce face fiecare linie:
- Description: numele lizibil afisat in
systemctl statussi in loguri. - After= si Wants=network-online.target:
Wants=aduce target-ul “reteaua e configurata”, iarAfter=face serviciul nostru sa-l astepte. Conteaza daca aplicatia se conecteaza la o baza de date la distanta cand porneste. - StartLimitIntervalSec si StartLimitBurst: daca serviciul porneste de mai mult de 5 ori in 60 de secunde, systemd renunta in loc sa intre intr-o bucla infinita. Ambele stau in
[Unit], nu in[Service], o greseala foarte frecventa. - Type=exec: serviciul e considerat pornit dupa ce programul chiar a fost executat. Daca ai gresit calea,
systemctl startda imediat o eroare clara.Type=simplee varianta implicita mai veche;Type=notifye pentru aplicatiile care isi raporteaza singure starea de “gata”. - User si Group: contul de la Pasul 2.
- WorkingDirectory: caile relative din aplicatie se rezolva de aici.
- EnvironmentFile: fisierul de la Pasul 4.
- ExecStart: comanda rulata. Trebuie sa inceapa cu o cale absoluta si nu exista shell, deci pipe-urile,
&&, redirectarile cu>si~nu functioneaza. - Restart=on-failure: reporneste dupa un cod de iesire diferit de zero, un semnal de crash sau un timeout, dar nu dupa o iesire curata sau dupa
systemctl stop. FolosesteRestart=alwayspentru workeri care ies intentionat si trebuie sa revina. - RestartSec=5: asteapta cinci secunde inainte de restart, ca o bucla de crash-uri sa nu arda procesorul degeaba.
- TimeoutStopSec=20: la oprire, systemd trimite SIGTERM, asteapta 20 de secunde o inchidere curata, apoi trimite SIGKILL.
- SyslogIdentifier: eticheta din fata fiecarei linii de log.
- WantedBy=multi-user.target: asta foloseste
enableca sa porneasca serviciul la boot.
Fisierele unit pot fi citite de orice user de pe sistem, si exact de aceea secretele stau in fisierul de mediu. Verifica sintaxa inainte sa mergi mai departe:
sudo systemd-analyze verify /etc/systemd/system/myapp.service
Daca nu afiseaza nimic, nu a gasit probleme.
Pasul 6: Activeaza, porneste si testeaza restartul automat
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.service --no-pager
daemon-reload ii spune lui systemd sa recitesca fisierele unit de pe disc (necesar de fiecare data cand editezi unul). enable creeaza legatura care porneste serviciul la boot, iar --now il si porneste pe loc. Statusul ar trebui sa arate cam asa:
● myapp.service - My example web app
Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-11-10 10:15:02 EET; 4s ago
Main PID: 2481 (node)
Tasks: 7 (limit: 2307)
Memory: 38.5M
Nov 10 10:15:02 vps systemd[1]: Started myapp.service - My example web app.
Nov 10 10:15:02 vps myapp[2481]: listening on 127.0.0.1:3000
Partile importante sunt enabled (porneste la boot) si active (running). Testeaza cu curl http://127.0.0.1:3000, care ar trebui sa raspunda Salut from myapp, pid 2481: mesajul vine acum din fisierul de mediu.
Acum simulam un crash, omorand procesul cu forta:
sudo systemctl kill --signal=SIGKILL myapp.service
sleep 7
systemctl status myapp.service --no-pager
Serviciul ruleaza din nou, cu alt Main PID, iar in loguri apare Main process exited, code=killed, status=9/KILL urmat de Scheduled restart job, restart counter is at 1. Asta e plasa ta de siguranta in actiune. La final, testul real: ruleaza sudo reboot, reconecteaza-te dupa un minut si verifica iar systemctl status myapp. Ar trebui sa ruleze fara sa atingi nimic.
Comenzile de zi cu zi de acum incolo: sudo systemctl restart myapp dupa un deploy, sudo systemctl stop myapp ca sa-l opresti (dupa o oprire voita nu exista restart automat), sudo systemctl disable myapp ca sa nu mai porneasca la boot, si systemctl is-active myapp sau systemctl is-enabled myapp pentru verificari rapide in scripturi.
Pasul 7: Citeste logurile cu journalctl
Pentru ca aplicatia scrie pe iesirea standard, fiecare linie ajunge in journal, etichetata cu numele serviciului si cu data si ora. Cele mai utile comenzi:
journalctl -u myapp -f # urmaresti live, ca tail -f
journalctl -u myapp -n 50 --no-pager # ultimele 50 de linii
journalctl -u myapp --since "1 hour ago" # interval de timp
journalctl -u myapp -b # doar de la ultimul boot
journalctl -u myapp -p err # doar erori si mai grave
-u filtreaza dupa unit, -f urmareste liniile noi (iesi cu Ctrl+C), -b limiteaza la boot-ul curent, iar -p err arata doar mesajele cu prioritate de eroare sau mai mare. Adauga -o short-iso pentru ore in format ISO sau -o cat ca sa vezi doar textul mesajului.
Permisiunile difera intre distributii. Pe Ubuntu, primul user e membru al grupului adm si poate citi toate logurile. Pe Debian, userul creat la instalare de multe ori nu este, asa ca vezi doar mesajele tale. Fie pui sudo in fata comenzilor, fie te adaugi in grupul systemd-journal si te loghezi din nou:
sudo usermod -aG systemd-journal "$USER"
Asigura-te ca logurile supravietuiesc unui reboot. Versiunile actuale de Debian si Ubuntu pastreaza de obicei journal-ul persistent, in /var/log/journal. Verifica cu ls /var/log/journal si journalctl --disk-usage. Daca directorul lipseste, logurile stau doar in memorie; creeaza-l si reporneste journald:
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
Ca sa limitezi spatiul ocupat, foloseste un fisier drop-in in loc sa editezi journald.conf (daca fisierul de mai jos exista deja, adauga setarea in el, nu-l suprascrie):
sudo mkdir -p /etc/systemd/journald.conf.d
sudo nano /etc/systemd/journald.conf.d/size.conf
[Journal]
SystemMaxUse=500M
Aplica modificarea cu sudo systemctl restart systemd-journald.
Pasul 8: Adauga sandboxing si verifica scorul de securitate
Serviciul nostru ruleaza deja cu un user fara privilegii, dar inca poate citi mare parte din sistemul de fisiere, poate vedea alte procese si poate apela aproape orice functie a kernelului. systemd poate reduce drastic toate astea cu cateva linii. Mai intai, masuram unde suntem:
systemd-analyze security myapp.service
Primesti un tabel lung cu verificari si, la final, o linie de genul Overall exposure level for myapp.service: 9.2 UNSAFE, urmata de o mica pictograma. Scorul merge de la 0 (blocat complet) la 10 (complet expus). Nu te speria: “UNSAFE” inseamna doar “fara sandboxing configurat”, adica starea implicita a oricarui serviciu.
Deschide din nou fisierul unit (sudo nano /etc/systemd/system/myapp.service) si adauga liniile urmatoare la finalul sectiunii [Service], chiar inainte de [Install]:
# Director de date cu drept de scriere: /var/lib/myapp, exportat ca $STATE_DIRECTORY
StateDirectory=myapp
UMask=0077
# Sistem de fisiere
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
# Privilegii
NoNewPrivileges=yes
CapabilityBoundingSet=
RestrictSUIDSGID=yes
# Kernel si sistem
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
ProtectProc=invisible
# Retea, namespace-uri, apeluri de sistem
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes
LockPersonality=yes
RemoveIPC=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged
SystemCallErrorNumber=EPERM
# Limite de resurse
MemoryMax=512M
TasksMax=200
Pe intelesul tuturor:
- ProtectSystem=strict face tot sistemul de fisiere doar pentru citire pentru aplicatie, cu exceptia directoarelor permise explicit. StateDirectory=myapp e exact o astfel de permisiune: systemd creeaza
/var/lib/myapp, detinut demyapp, si ii transmite calea in variabilaSTATE_DIRECTORY. Acolo pastrezi upload-uri, fisiere SQLite sau cache. UMask=0077 face fisierele noi private pentru serviciu. - ProtectHome ascunde
/homesi/root, PrivateTmp ii da aplicatiei propriul/tmpgol, PrivateDevices ascunde dispozitivele hardware. - NoNewPrivileges, un CapabilityBoundingSet gol si RestrictSUIDSGID garanteaza ca procesul nu poate obtine niciodata puteri de root, nici macar printr-un binar setuid.
- Optiunile Protect… pentru kernel il opresc sa schimbe setari de kernel, sa incarce module, sa citeasca logurile kernelului, sa modifice ceasul sau hostname-ul. ProtectProc=invisible ascunde procesele altor useri.
- RestrictAddressFamilies permite doar IPv4, IPv6 si socket-uri Unix locale (adauga
AF_NETLINKdaca aplicatia listeaza interfetele de retea). SystemCallFilter permite apelurile de sistem de care are nevoie un serviciu normal si le blocheaza pe cele privilegiate; cuSystemCallErrorNumber=EPERM, un apel blocat intoarce o eroare in loc sa omoare procesul. - MemoryMax si TasksMax impiedica un memory leak sau un fork bomb sa doboare tot VPS-ul.
Am lasat intentionat pe dinafara MemoryDenyWriteExecute=yes: strica Node.js, al carui motor JavaScript genereaza cod masina in timpul rularii. E o adaugire buna pentru binarele Go (vezi mai jos). Aplica si masoara din nou:
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl status myapp.service --no-pager
curl http://127.0.0.1:3000
systemd-analyze security myapp.service | tail -n 1
Aplicatia raspunde ca inainte, iar ultima linie arata acum cam asa: Overall exposure level for myapp.service: 2.1 OK. Cifrele exacte depind de versiunea systemd (Debian 13 vine cu systemd 257, Ubuntu 24.04 cu 255).
O nota pentru containerele LXC. Daca VPS-ul tau e un container LXC neprivilegiat si nu o masina virtuala KVM, systemd ruleaza in interiorul containerului si nu poate crea intotdeauna namespace-urile pe care se bazeaza unele dintre optiunile astea. Simptomul e un serviciu care cade cu status=226/NAMESPACE. Scoate optiunile pe rand, incepand cu PrivateDevices, ProtectClock, ProtectKernelLogs, ProtectKernelModules si ProtectProc, pana porneste, sau intreaba furnizorul daca se poate activa nesting. Pe un VPS KVM totul merge exact cum e descris.
Cum adaptezi unit-ul pentru aplicatii Python si Go
Pasii 2, 3, 4, 6, 7 si 8 raman la fel. Se schimba doar modul in care instalezi aplicatia si linia ExecStart=.
Python
Aplicatiile Python ar trebui sa ruleze dintr-un mediu virtual (venv), un folder izolat care contine legatura catre interpretor si bibliotecile aplicatiei. Pe Debian si Ubuntu ai nevoie de pachetul python3-venv. Iata aceeasi aplicatie de exemplu, in Python, salvata ca ~/myapp/app.py:
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
PORT = int(os.environ.get("PORT", "3000"))
GREETING = os.environ.get("GREETING", "Hello")
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = f"{GREETING} from myapp, pid {os.getpid()}\n".encode()
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(body)
print(f"listening on 127.0.0.1:{PORT}", flush=True)
HTTPServer(("127.0.0.1", PORT), Handler).serve_forever()
O instalezi cu un venv (sari peste linia cu pip daca nu ai requirements.txt):
sudo apt install -y python3-venv
sudo mkdir -p /opt/myapp
sudo cp -r ~/myapp/. /opt/myapp/
sudo python3 -m venv /opt/myapp/venv
sudo /opt/myapp/venv/bin/pip install -r /opt/myapp/requirements.txt
sudo chown -R root:myapp /opt/myapp
sudo chmod -R u=rwX,g=rX,o= /opt/myapp
In fisierul unit, inlocuieste linia ExecStart= si adauga o linie de mediu:
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/myapp/venv/bin/python /opt/myapp/app.py
PYTHONUNBUFFERED=1 il face pe Python sa scrie liniile de log imediat, nu sa le tina in buffer, altfel ajung in journal tarziu sau in rafale. Pentru o aplicatie Flask sau Django, ruleaza un server de productie, de exemplu ExecStart=/opt/myapp/venv/bin/gunicorn --bind 127.0.0.1:3000 --workers 2 app:app.
Go
Go compileaza intr-un singur binar static, deci e cazul cel mai simplu. Il construiesti pe statia ta pentru arhitectura serverului (majoritatea VPS-urilor sunt amd64; foloseste arm64 pentru servere ARM), il copiezi si il instalezi cu proprietarul si permisiunile corecte intr-o singura comanda:
# pe statia ta de lucru
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o myapp .
scp myapp userultau@ip-vps:/tmp/
# pe VPS
sudo mkdir -p /opt/myapp
sudo install -o root -g myapp -m 0750 /tmp/myapp /opt/myapp/myapp
Unit-ul foloseste ExecStart=/opt/myapp/myapp. Programele Go nu genereaza cod in timpul rularii, asa ca poti adauga si MemoryDenyWriteExecute=yes in blocul de hardening, pentru un scor si mai mic.
Cum publici o versiune noua
Actualizarea aplicatiei inseamna mereu aceiasi trei pasi: copiezi fisierele noi, repari proprietarul, repornesti.
sudo cp -r ~/myapp/. /opt/myapp/
sudo chown -R root:myapp /opt/myapp
sudo chmod -R u=rwX,g=rX,o= /opt/myapp
sudo systemctl restart myapp.service
journalctl -u myapp -n 20 --no-pager
Daca modifici fisierul de mediu, reporneste si atunci: variabilele sunt citite doar la pornirea serviciului. Daca modifici fisierul unit, ruleaza sudo systemctl daemon-reload inainte de restart.
Depanare si greseli frecvente
Cand un serviciu cade, systemctl status myapp iti arata un cod de iesire, iar journalctl -u myapp -n 50 iti arata motivul. Cele mai des intalnite:
status=203/EXEC: systemd nu a putut executaExecStart. Calea e gresita, nu e absoluta, fisierul nu e executabil, sau runtime-ul e instalat in home-ul tau (nvm, pyenv), unde userulmyappnu ajunge. Foloseste un runtime instalat la nivel de sistem si calea dincommand -v node.status=200/CHDIR:WorkingDirectorylipseste sau nu poate fi citit de userul serviciului. Testeaza cusudo -u myapp ls /opt/myapp.status=217/USER: userul dinUser=nu exista. Verifica cuid myapp.status=226/NAMESPACE: sandbox-ul nu a putut fi construit, de obicei intr-un container LXC neprivilegiat (vezi Pasul 8), sau o cale dinReadWritePaths=nu exista.- “The unit file changed on disk”: ai uitat
sudo systemctl daemon-reloaddupa editare. - “Start request repeated too quickly”: s-a atins limita de porniri. Citeste journal-ul, rezolva cauza, apoi ruleaza
sudo systemctl reset-failed myapp.servicesi porneste din nou. EROFSsauEACCESla scrierea fisierelor: asta eProtectSystem=strictcare isi face treaba. Scrie in$STATE_DIRECTORYsau adauga o linieReadWritePaths=pentru folderul exact.- Variabilele de mediu nu se aplica: verifica formatul fisierului (fara
export, fara spatii in jurul lui=) si nu uita de restart dupa modificari. - Un port sub 1024 da
EACCES: userii fara privilegii nu pot asculta pe porturi ca 80 sau 443. Pune un reverse proxy in fata, sau permite explicit cuAmbientCapabilities=CAP_NET_BIND_SERVICEplusCapabilityBoundingSet=CAP_NET_BIND_SERVICE. - Merge pornita manual, dar nu ca serviciu: rul-o exact cum o face systemd,
sudo -u myapp /usr/bin/node /opt/myapp/server.js, si citeste eroarea.
Note de securitate
- Nu rula niciodata aplicatii ca root. Userul dedicat nu are shell, parola sau drepturi sudo; da fiecarei aplicatii propriul user.
- Codul e doar pentru citire pentru aplicatie.
/opt/myappe detinut de root, deci nici o aplicatie compromisa nu se poate modifica singura. Doar/var/lib/myapppermite scriere. - Tine secretele departe de fisierele unit. Fisierele unit si iesirea comenzii
systemctl showpot fi citite de orice user. Secretele merg in fisierul de mediu cu permisiuni600. Tine cont ca variabilele de mediu raman vizibile pentru root si pentru procesele aceluiasi user; pentru o izolare mai buna, uita-te laLoadCredential=din systemd, care expune secretele ca fisiere in$CREDENTIALS_DIRECTORY. - Nu expune portul aplicatiei. Ea asculta pe 127.0.0.1, iar traficul public trece printr-un reverse proxy cu TLS. Daca chiar trebuie sa asculte pe toate interfetele, deschide portul constient, cu
sudo ufw allow 3000/tcp. Fiind un serviciu obisnuit al hostului, ufw chiar il filtreaza (spre deosebire de porturile publicate de Docker, care ocolesc ufw). - Sandboxing-ul e aparare in profunzime, nu inlocuieste update-urile. Tine sistemul si runtime-ul la zi cu
sudo apt update && sudo apt upgrade. Pe Ubuntu, reporneste cand exista/var/run/reboot-required; Debian nu creeaza intotdeauna fisierul asta, asa ca reporneste ori de cate ori s-a instalat un pachetlinux-imagenou. Serviciul tau va reveni singur. - Foloseste limitele.
MemoryMaxsiTasksMaximpiedica o singura aplicatie scapata de sub control sa lase fara resurse tot restul VPS-ului.
Deci, iti ajunge un serviciu systemd?
Pentru cele mai multe aplicatii de sine statatoare pe un VPS, da. Cu un singur fisier unit de vreo 50 de linii primesti pornire la boot, restart automat cu o limita rezonabila pentru buclele de crash, user dedicat, secrete tinute departe de cod, loguri centralizate in care poti cauta si un sandbox care transforma un scor “UNSAFE” intr-unul “OK”. Fara daemon in plus, fara alt runtime, iar aceeasi reteta merge pentru Node.js, Python, Go si orice altceva care ruleaza in foreground. Cand cresti, aceleasi cunostinte te ajuta la timere, socket activation si unit-uri de tip template.
Ia un VPS de la noi si poti avea aplicatia rulata ca serviciu securizat in douazeci de minute. Daca preferi ca altcineva sa-ti scrie, sa-ti securizeze si sa-ti monitorizeze serviciile, echipa noastra de administrare servere face exact asta in fiecare zi.
Intrebari frecvente
Care e diferenta dintre enable si start?
start porneste serviciul acum, pana la urmatorul reboot sau stop. enable il face sa porneasca automat la fiecare boot, dar nu il porneste acum. enable --now le face pe amandoua, si de obicei asta vrei.
Pot rula mai multe copii ale aceleiasi aplicatii?
Da, cu un unit de tip template. Numeste fisierul myapp@.service si foloseste %i in el, de exemplu EnvironmentFile=/etc/myapp/%i.env. Apoi sudo systemctl enable --now myapp@magazin si myapp@blog ruleaza doua instante independente, fiecare cu configuratia ei.
Pot crea un serviciu fara sudo?
Da, ca serviciu de user: pui unit-ul in ~/.config/systemd/user/, il gestionezi cu systemctl --user, iar un administrator ruleaza o singura data sudo loginctl enable-linger numeletau, ca serviciul sa ruleze si dupa ce te deloghezi si sa porneasca la boot. Pe un server pe care il controlezi, serviciile de sistem descrise aici raman alegerea mai buna.
Trebuie sa editez fisierele unit instalate de pachete?
Nu. Foloseste sudo systemctl edit nginx.service, care creeaza un fisier de override in /etc/systemd/system/nginx.service.d/, pastrat la update-urile pachetului. Unit-urile tale din /etc/systemd/system/ le poti edita direct.
Mai am nevoie de PM2 sau supervisord?
Nu ca sa tii aplicatia in viata. systemd acopera deja pornirea la boot, restartul, logurile si limitele de resurse. Modul cluster din PM2 poate fi in continuare util pentru Node.js, dar poti rula si mai multe instante cu un unit template, in spatele unui reverse proxy.