Ilustratie cu un serviciu systemd care porneste o aplicatie pe un server Linux, cu restart si sandboxing
Linux

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 numi myapp.service.
  • Target: un grup de unit-uri cu nume, care marcheaza o etapa a boot-ului. multi-user.target inseamna “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-journald si citit cu comanda journalctl.

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

Cum ruleaza systemd aplicatia taBoot sausystemctl startsystemdPID 1, supraveghetormyapp.serviceUser=myappRestart=on-failureProtectSystem=strict/etc/myapp/myapp.envvariabile si secreteSandboxnode /opt/myapp/server.jsruleaza ca user myapp/usr /etcdoar citire/var/lib/myappscriere permisa/tmp privat, fara privilegii noifara capabilitati, syscall-uri filtratecrash: restart dupa RestartSec=5stdout, stderrjournaldpastreaza logurilejournalctl-u myapp -fgestionat de systemdlimita sandbox-uluiAplicatia asculta pe 127.0.0.1:3000; in fata poate sta un reverse proxy.
systemd citeste fisierul unit si fisierul de mediu, porneste aplicatia ca user myapp intr-un sandbox, o reporneste dupa un crash si strange in journal tot ce afiseaza.

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

Un serviciu systemd in 8 pasi1Testeaza aplicatiapornita manual2Creeaza userulcont de sistem3Instaleaza in /optcod detinut de root4Fisier de mediu/etc/myapp5Fisierul unitmyapp.service6Enable + starttest de restart7Citeste logurilejournalctl8Sandboxingscor de securitate
Cei opt pasi ai tutorialului, de la Pasul 1 (testezi aplicatia manual) la Pasul 8 (sandboxing si scorul de securitate). Fiecare numar corespunde unui titlu de mai jos.

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 status si in loguri.
  • After= si Wants=network-online.target: Wants= aduce target-ul “reteaua e configurata”, iar After= 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 start da imediat o eroare clara. Type=simple e varianta implicita mai veche; Type=notify e 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. Foloseste Restart=always pentru 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 enable ca 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 de myapp, si ii transmite calea in variabila STATE_DIRECTORY. Acolo pastrezi upload-uri, fisiere SQLite sau cache. UMask=0077 face fisierele noi private pentru serviciu.
  • ProtectHome ascunde /home si /root, PrivateTmp ii da aplicatiei propriul /tmp gol, 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_NETLINK daca aplicatia listeaza interfetele de retea). SystemCallFilter permite apelurile de sistem de care are nevoie un serviciu normal si le blocheaza pe cele privilegiate; cu SystemCallErrorNumber=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).

systemd-analyze security: scor de expunere (aprox.)Mai mic e mai bine. Valorile variaza cu versiunea systemd.Root, fara optiuni9.6 UNSAFEDoar User=myapp9.2 UNSAFE+ reguli de fisiere6.4 MEDIUMHardening complet2.1 OK0246810Nivel general de expunere (0 = blocat, 10 = complet expus)Configuratia unit-uluiCalificative: 9.0+ UNSAFE, 7.5+ EXPOSED, 5.0+ MEDIUM, 1.0+ OK, sub 1.0 SAFE
Scoruri de expunere aproximative pentru exemplul de la Pasul 8. "+ reguli de fisiere" inseamna doar NoNewPrivileges, ProtectSystem, ProtectHome si PrivateTmp; "Hardening complet" e tot blocul de mai sus.

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 executa ExecStart. Calea e gresita, nu e absoluta, fisierul nu e executabil, sau runtime-ul e instalat in home-ul tau (nvm, pyenv), unde userul myapp nu ajunge. Foloseste un runtime instalat la nivel de sistem si calea din command -v node.
  • status=200/CHDIR: WorkingDirectory lipseste sau nu poate fi citit de userul serviciului. Testeaza cu sudo -u myapp ls /opt/myapp.
  • status=217/USER: userul din User= nu exista. Verifica cu id myapp.
  • status=226/NAMESPACE: sandbox-ul nu a putut fi construit, de obicei intr-un container LXC neprivilegiat (vezi Pasul 8), sau o cale din ReadWritePaths= nu exista.
  • “The unit file changed on disk”: ai uitat sudo systemctl daemon-reload dupa editare.
  • “Start request repeated too quickly”: s-a atins limita de porniri. Citeste journal-ul, rezolva cauza, apoi ruleaza sudo systemctl reset-failed myapp.service si porneste din nou.
  • EROFS sau EACCES la scrierea fisierelor: asta e ProtectSystem=strict care isi face treaba. Scrie in $STATE_DIRECTORY sau adauga o linie ReadWritePaths= 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 cu AmbientCapabilities=CAP_NET_BIND_SERVICE plus CapabilityBoundingSet=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/myapp e detinut de root, deci nici o aplicatie compromisa nu se poate modifica singura. Doar /var/lib/myapp permite scriere.
  • Tine secretele departe de fisierele unit. Fisierele unit si iesirea comenzii systemctl show pot fi citite de orice user. Secretele merg in fisierul de mediu cu permisiuni 600. Tine cont ca variabilele de mediu raman vizibile pentru root si pentru procesele aceluiasi user; pentru o izolare mai buna, uita-te la LoadCredential= 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 pachet linux-image nou. Serviciul tau va reveni singur.
  • Foloseste limitele. MemoryMax si TasksMax impiedica 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.

Avem Incredere & Suntem Membri

Suntem membri ai principalelor organizatii de infrastructura internet.

RIPE NCC MANRS PeeringDB RoTLD DSIX SBIX 4IXP LOCIX Euro-IX RIPE NCC MANRS PeeringDB RoTLD DSIX SBIX 4IXP LOCIX Euro-IX