[{"data":1,"prerenderedAt":1635},["ShallowReactive",2],{"page-en-\u002Fblog\u002Fsystemd-service-guide":3,"related-en-\u002Fblog\u002Fsystemd-service-guide":1612},{"id":4,"title":5,"body":6,"date":1599,"description":1600,"extension":1601,"meta":1602,"navigation":273,"path":1603,"seo":1604,"stem":1605,"tags":1606,"translation":1610,"__hash__":1611},"pages_en\u002Fen\u002Fblog\u002Fsystemd-service-guide.md","Writing a Linux Service with systemd",{"type":7,"value":8,"toc":1586},"minimark",[9,14,23,34,38,208,212,219,385,443,446,509,519,529,533,654,757,764,768,774,804,823,826,899,903,959,962,990,1000,1004,1007,1184,1198,1202,1205,1262,1275,1279,1282,1340,1408,1460,1466,1470,1563,1567,1582],[10,11,13],"h2",{"id":12},"introduction","Introduction",[15,16,17,18,22],"p",{},"Starting a Java jar, a Node.js app or a Python script on a server with ",[19,20,21],"code",{},"nohup ... &"," works at first glance; but when the process crashes nobody restarts it, it doesn't come back after a reboot, and the logs end up scattered.",[15,24,25,29,30,33],{},[26,27,28],"strong",{},"systemd",", present on every modern Linux distribution (Ubuntu, Debian, AlmaLinux, RHEL), solves all of this without installing anything: it starts the process, restarts it on crash, runs it at boot, and collects its logs in ",[19,31,32],{},"journald",".",[10,35,37],{"id":36},"_1-basic-systemctl-commands","1. Basic systemctl commands",[39,40,45],"pre",{"className":41,"code":42,"language":43,"meta":44,"style":44},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","sudo systemctl start myapp       # start\nsudo systemctl stop myapp        # stop\nsudo systemctl restart myapp     # restart\nsudo systemctl reload myapp      # reload configuration (if supported)\nsudo systemctl enable myapp      # start automatically at boot\nsudo systemctl enable --now myapp    # enable + start\nsudo systemctl disable myapp\nsystemctl status myapp           # state, PID, last log lines\nsystemctl is-active myapp\nsystemctl list-units --type=service --state=running\nsystemctl list-units --failed\n","bash","",[19,46,47,70,85,100,115,130,147,160,174,184,198],{"__ignoreMap":44},[48,49,52,56,60,63,66],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sBMFI","sudo",[48,57,59],{"class":58},"sfazB"," systemctl",[48,61,62],{"class":58}," start",[48,64,65],{"class":58}," myapp",[48,67,69],{"class":68},"sHwdD","       # start\n",[48,71,73,75,77,80,82],{"class":50,"line":72},2,[48,74,55],{"class":54},[48,76,59],{"class":58},[48,78,79],{"class":58}," stop",[48,81,65],{"class":58},[48,83,84],{"class":68},"        # stop\n",[48,86,88,90,92,95,97],{"class":50,"line":87},3,[48,89,55],{"class":54},[48,91,59],{"class":58},[48,93,94],{"class":58}," restart",[48,96,65],{"class":58},[48,98,99],{"class":68},"     # restart\n",[48,101,103,105,107,110,112],{"class":50,"line":102},4,[48,104,55],{"class":54},[48,106,59],{"class":58},[48,108,109],{"class":58}," reload",[48,111,65],{"class":58},[48,113,114],{"class":68},"      # reload configuration (if supported)\n",[48,116,118,120,122,125,127],{"class":50,"line":117},5,[48,119,55],{"class":54},[48,121,59],{"class":58},[48,123,124],{"class":58}," enable",[48,126,65],{"class":58},[48,128,129],{"class":68},"      # start automatically at boot\n",[48,131,133,135,137,139,142,144],{"class":50,"line":132},6,[48,134,55],{"class":54},[48,136,59],{"class":58},[48,138,124],{"class":58},[48,140,141],{"class":58}," --now",[48,143,65],{"class":58},[48,145,146],{"class":68},"    # enable + start\n",[48,148,150,152,154,157],{"class":50,"line":149},7,[48,151,55],{"class":54},[48,153,59],{"class":58},[48,155,156],{"class":58}," disable",[48,158,159],{"class":58}," myapp\n",[48,161,163,166,169,171],{"class":50,"line":162},8,[48,164,165],{"class":54},"systemctl",[48,167,168],{"class":58}," status",[48,170,65],{"class":58},[48,172,173],{"class":68},"           # state, PID, last log lines\n",[48,175,177,179,182],{"class":50,"line":176},9,[48,178,165],{"class":54},[48,180,181],{"class":58}," is-active",[48,183,159],{"class":58},[48,185,187,189,192,195],{"class":50,"line":186},10,[48,188,165],{"class":54},[48,190,191],{"class":58}," list-units",[48,193,194],{"class":58}," --type=service",[48,196,197],{"class":58}," --state=running\n",[48,199,201,203,205],{"class":50,"line":200},11,[48,202,165],{"class":54},[48,204,191],{"class":58},[48,206,207],{"class":58}," --failed\n",[10,209,211],{"id":210},"_2-a-first-unit-file","2. A first unit file",[15,213,214,215,218],{},"Unit files for your own services go in ",[19,216,217],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002F",". Here's a Spring Boot app:",[39,220,224],{"className":221,"code":222,"language":223,"meta":44,"style":44},"language-ini shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fmyapp.service\n[Unit]\nDescription=My Spring Boot App\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=myapp\nGroup=myapp\nWorkingDirectory=\u002Fopt\u002Fmyapp\nExecStart=\u002Fusr\u002Fbin\u002Fjava -Xmx512m -jar \u002Fopt\u002Fmyapp\u002Fapp.jar\nRestart=on-failure\nRestartSec=5\nSuccessExitStatus=143\n\n[Install]\nWantedBy=multi-user.target\n","ini",[19,225,226,231,237,250,260,269,275,280,290,300,309,319,330,341,352,363,368,374],{"__ignoreMap":44},[48,227,228],{"class":50,"line":51},[48,229,230],{"class":68},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fmyapp.service\n",[48,232,233],{"class":50,"line":72},[48,234,236],{"class":235},"sMK4o","[Unit]\n",[48,238,239,243,246],{"class":50,"line":87},[48,240,242],{"class":241},"swJcz","Description",[48,244,245],{"class":235},"=",[48,247,249],{"class":248},"sTEyZ","My Spring Boot App\n",[48,251,252,255,257],{"class":50,"line":102},[48,253,254],{"class":241},"After",[48,256,245],{"class":235},[48,258,259],{"class":248},"network-online.target\n",[48,261,262,265,267],{"class":50,"line":117},[48,263,264],{"class":241},"Wants",[48,266,245],{"class":235},[48,268,259],{"class":248},[48,270,271],{"class":50,"line":132},[48,272,274],{"emptyLinePlaceholder":273},true,"\n",[48,276,277],{"class":50,"line":149},[48,278,279],{"class":235},"[Service]\n",[48,281,282,285,287],{"class":50,"line":162},[48,283,284],{"class":241},"Type",[48,286,245],{"class":235},[48,288,289],{"class":248},"simple\n",[48,291,292,295,297],{"class":50,"line":176},[48,293,294],{"class":241},"User",[48,296,245],{"class":235},[48,298,299],{"class":248},"myapp\n",[48,301,302,305,307],{"class":50,"line":186},[48,303,304],{"class":241},"Group",[48,306,245],{"class":235},[48,308,299],{"class":248},[48,310,311,314,316],{"class":50,"line":200},[48,312,313],{"class":241},"WorkingDirectory",[48,315,245],{"class":235},[48,317,318],{"class":248},"\u002Fopt\u002Fmyapp\n",[48,320,322,325,327],{"class":50,"line":321},12,[48,323,324],{"class":241},"ExecStart",[48,326,245],{"class":235},[48,328,329],{"class":248},"\u002Fusr\u002Fbin\u002Fjava -Xmx512m -jar \u002Fopt\u002Fmyapp\u002Fapp.jar\n",[48,331,333,336,338],{"class":50,"line":332},13,[48,334,335],{"class":241},"Restart",[48,337,245],{"class":235},[48,339,340],{"class":248},"on-failure\n",[48,342,344,347,349],{"class":50,"line":343},14,[48,345,346],{"class":241},"RestartSec",[48,348,245],{"class":235},[48,350,351],{"class":248},"5\n",[48,353,355,358,360],{"class":50,"line":354},15,[48,356,357],{"class":241},"SuccessExitStatus",[48,359,245],{"class":235},[48,361,362],{"class":248},"143\n",[48,364,366],{"class":50,"line":365},16,[48,367,274],{"emptyLinePlaceholder":273},[48,369,371],{"class":50,"line":370},17,[48,372,373],{"class":235},"[Install]\n",[48,375,377,380,382],{"class":50,"line":376},18,[48,378,379],{"class":241},"WantedBy",[48,381,245],{"class":235},[48,383,384],{"class":248},"multi-user.target\n",[39,386,388],{"className":41,"code":387,"language":43,"meta":44,"style":44},"sudo useradd --system --no-create-home --shell \u002Fusr\u002Fsbin\u002Fnologin myapp\nsudo systemctl daemon-reload     # read the new\u002Fchanged unit file\nsudo systemctl enable --now myapp\nsystemctl status myapp\n",[19,389,390,411,423,435],{"__ignoreMap":44},[48,391,392,394,397,400,403,406,409],{"class":50,"line":51},[48,393,55],{"class":54},[48,395,396],{"class":58}," useradd",[48,398,399],{"class":58}," --system",[48,401,402],{"class":58}," --no-create-home",[48,404,405],{"class":58}," --shell",[48,407,408],{"class":58}," \u002Fusr\u002Fsbin\u002Fnologin",[48,410,159],{"class":58},[48,412,413,415,417,420],{"class":50,"line":72},[48,414,55],{"class":54},[48,416,59],{"class":58},[48,418,419],{"class":58}," daemon-reload",[48,421,422],{"class":68},"     # read the new\u002Fchanged unit file\n",[48,424,425,427,429,431,433],{"class":50,"line":87},[48,426,55],{"class":54},[48,428,59],{"class":58},[48,430,124],{"class":58},[48,432,141],{"class":58},[48,434,159],{"class":58},[48,436,437,439,441],{"class":50,"line":102},[48,438,165],{"class":54},[48,440,168],{"class":58},[48,442,159],{"class":58},[15,444,445],{},"What the sections mean:",[447,448,449,462],"table",{},[450,451,452],"thead",{},[453,454,455,459],"tr",{},[456,457,458],"th",{},"Section",[456,460,461],{},"Purpose",[463,464,465,485,495],"tbody",{},[453,466,467,473],{},[468,469,470],"td",{},[19,471,472],{},"[Unit]",[468,474,475,476,478,479,478,481,484],{},"Description and relationships to other units (",[19,477,254],{},", ",[19,480,264],{},[19,482,483],{},"Requires",")",[453,486,487,492],{},[468,488,489],{},[19,490,491],{},"[Service]",[468,493,494],{},"How the process runs: command, user, restart policy",[453,496,497,502],{},[468,498,499],{},[19,500,501],{},"[Install]",[468,503,504,505,508],{},"Which target it hooks into when enabled; ",[19,506,507],{},"multi-user.target"," for servers",[15,510,511,514,515,518],{},[19,512,513],{},"SuccessExitStatus=143"," stops the 143 exit code Java returns on ",[19,516,517],{},"SIGTERM"," from counting as a failure.",[520,521,522],"blockquote",{},[15,523,524,525,528],{},"Run ",[19,526,527],{},"systemctl daemon-reload"," every time you change a unit file; otherwise systemd keeps using the old version.",[10,530,532],{"id":531},"_3-nodejs-and-python-examples","3. Node.js and Python examples",[39,534,536],{"className":221,"code":535,"language":223,"meta":44,"style":44},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fapi.service\n[Unit]\nDescription=Node.js API\nAfter=network.target\n\n[Service]\nUser=deploy\nWorkingDirectory=\u002Fvar\u002Fwww\u002Fapi\nExecStart=\u002Fusr\u002Fbin\u002Fnode server.js\nRestart=always\nEnvironment=NODE_ENV=production\nEnvironment=PORT=3000\n\n[Install]\nWantedBy=multi-user.target\n",[19,537,538,543,547,556,565,569,573,582,591,600,609,624,638,642,646],{"__ignoreMap":44},[48,539,540],{"class":50,"line":51},[48,541,542],{"class":68},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fapi.service\n",[48,544,545],{"class":50,"line":72},[48,546,236],{"class":235},[48,548,549,551,553],{"class":50,"line":87},[48,550,242],{"class":241},[48,552,245],{"class":235},[48,554,555],{"class":248},"Node.js API\n",[48,557,558,560,562],{"class":50,"line":102},[48,559,254],{"class":241},[48,561,245],{"class":235},[48,563,564],{"class":248},"network.target\n",[48,566,567],{"class":50,"line":117},[48,568,274],{"emptyLinePlaceholder":273},[48,570,571],{"class":50,"line":132},[48,572,279],{"class":235},[48,574,575,577,579],{"class":50,"line":149},[48,576,294],{"class":241},[48,578,245],{"class":235},[48,580,581],{"class":248},"deploy\n",[48,583,584,586,588],{"class":50,"line":162},[48,585,313],{"class":241},[48,587,245],{"class":235},[48,589,590],{"class":248},"\u002Fvar\u002Fwww\u002Fapi\n",[48,592,593,595,597],{"class":50,"line":176},[48,594,324],{"class":241},[48,596,245],{"class":235},[48,598,599],{"class":248},"\u002Fusr\u002Fbin\u002Fnode server.js\n",[48,601,602,604,606],{"class":50,"line":186},[48,603,335],{"class":241},[48,605,245],{"class":235},[48,607,608],{"class":248},"always\n",[48,610,611,614,616,619,621],{"class":50,"line":200},[48,612,613],{"class":241},"Environment",[48,615,245],{"class":235},[48,617,618],{"class":241},"NODE_ENV",[48,620,245],{"class":235},[48,622,623],{"class":248},"production\n",[48,625,626,628,630,633,635],{"class":50,"line":321},[48,627,613],{"class":241},[48,629,245],{"class":235},[48,631,632],{"class":241},"PORT",[48,634,245],{"class":235},[48,636,637],{"class":248},"3000\n",[48,639,640],{"class":50,"line":332},[48,641,274],{"emptyLinePlaceholder":273},[48,643,644],{"class":50,"line":343},[48,645,373],{"class":235},[48,647,648,650,652],{"class":50,"line":354},[48,649,379],{"class":241},[48,651,245],{"class":235},[48,653,384],{"class":248},[39,655,657],{"className":221,"code":656,"language":223,"meta":44,"style":44},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fworker.service\n[Unit]\nDescription=Python worker\nAfter=network.target\n\n[Service]\nUser=deploy\nWorkingDirectory=\u002Fopt\u002Fworker\nExecStart=\u002Fopt\u002Fworker\u002F.venv\u002Fbin\u002Fpython worker.py\nRestart=on-failure\nEnvironment=PYTHONUNBUFFERED=1\n\n[Install]\nWantedBy=multi-user.target\n",[19,658,659,664,668,677,685,689,693,701,710,719,727,741,745,749],{"__ignoreMap":44},[48,660,661],{"class":50,"line":51},[48,662,663],{"class":68},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fworker.service\n",[48,665,666],{"class":50,"line":72},[48,667,236],{"class":235},[48,669,670,672,674],{"class":50,"line":87},[48,671,242],{"class":241},[48,673,245],{"class":235},[48,675,676],{"class":248},"Python worker\n",[48,678,679,681,683],{"class":50,"line":102},[48,680,254],{"class":241},[48,682,245],{"class":235},[48,684,564],{"class":248},[48,686,687],{"class":50,"line":117},[48,688,274],{"emptyLinePlaceholder":273},[48,690,691],{"class":50,"line":132},[48,692,279],{"class":235},[48,694,695,697,699],{"class":50,"line":149},[48,696,294],{"class":241},[48,698,245],{"class":235},[48,700,581],{"class":248},[48,702,703,705,707],{"class":50,"line":162},[48,704,313],{"class":241},[48,706,245],{"class":235},[48,708,709],{"class":248},"\u002Fopt\u002Fworker\n",[48,711,712,714,716],{"class":50,"line":176},[48,713,324],{"class":241},[48,715,245],{"class":235},[48,717,718],{"class":248},"\u002Fopt\u002Fworker\u002F.venv\u002Fbin\u002Fpython worker.py\n",[48,720,721,723,725],{"class":50,"line":186},[48,722,335],{"class":241},[48,724,245],{"class":235},[48,726,340],{"class":248},[48,728,729,731,733,736,738],{"class":50,"line":200},[48,730,613],{"class":241},[48,732,245],{"class":235},[48,734,735],{"class":241},"PYTHONUNBUFFERED",[48,737,245],{"class":235},[48,739,740],{"class":248},"1\n",[48,742,743],{"class":50,"line":321},[48,744,274],{"emptyLinePlaceholder":273},[48,746,747],{"class":50,"line":332},[48,748,373],{"class":235},[48,750,751,753,755],{"class":50,"line":343},[48,752,379],{"class":241},[48,754,245],{"class":235},[48,756,384],{"class":248},[15,758,759,760,763],{},"Without ",[19,761,762],{},"PYTHONUNBUFFERED=1",", Python buffers its output and logs reach the journal late.",[10,765,767],{"id":766},"_4-environment-variables-and-secrets","4. Environment variables and secrets",[15,769,770,773],{},[19,771,772],{},"Environment="," is fine for a few variables. For many variables, or values containing passwords, use a separate file:",[39,775,777],{"className":41,"code":776,"language":43,"meta":44,"style":44},"# \u002Fetc\u002Fmyapp\u002Fmyapp.env  (chmod 600, owned by root)\nSPRING_PROFILES_ACTIVE=prod\nDB_PASSWORD=s3cret\n",[19,778,779,784,794],{"__ignoreMap":44},[48,780,781],{"class":50,"line":51},[48,782,783],{"class":68},"# \u002Fetc\u002Fmyapp\u002Fmyapp.env  (chmod 600, owned by root)\n",[48,785,786,789,791],{"class":50,"line":72},[48,787,788],{"class":248},"SPRING_PROFILES_ACTIVE",[48,790,245],{"class":235},[48,792,793],{"class":58},"prod\n",[48,795,796,799,801],{"class":50,"line":87},[48,797,798],{"class":248},"DB_PASSWORD",[48,800,245],{"class":235},[48,802,803],{"class":58},"s3cret\n",[39,805,807],{"className":221,"code":806,"language":223,"meta":44,"style":44},"[Service]\nEnvironmentFile=\u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[19,808,809,813],{"__ignoreMap":44},[48,810,811],{"class":50,"line":51},[48,812,279],{"class":235},[48,814,815,818,820],{"class":50,"line":72},[48,816,817],{"class":241},"EnvironmentFile",[48,819,245],{"class":235},[48,821,822],{"class":248},"\u002Fetc\u002Fmyapp\u002Fmyapp.env\n",[15,824,825],{},"To check that the variables reach the service:",[39,827,829],{"className":41,"code":828,"language":43,"meta":44,"style":44},"systemctl show myapp --property=Environment\nsudo cat \u002Fproc\u002F$(systemctl show -p MainPID --value myapp)\u002Fenviron | tr '\\0' '\\n'\n",[19,830,831,843],{"__ignoreMap":44},[48,832,833,835,838,840],{"class":50,"line":51},[48,834,165],{"class":54},[48,836,837],{"class":58}," show",[48,839,65],{"class":58},[48,841,842],{"class":58}," --property=Environment\n",[48,844,845,847,850,853,856,858,860,863,866,869,871,873,876,879,882,885,888,891,893,896],{"class":50,"line":72},[48,846,55],{"class":54},[48,848,849],{"class":58}," cat",[48,851,852],{"class":58}," \u002Fproc\u002F",[48,854,855],{"class":235},"$(",[48,857,165],{"class":54},[48,859,837],{"class":58},[48,861,862],{"class":58}," -p",[48,864,865],{"class":58}," MainPID",[48,867,868],{"class":58}," --value",[48,870,65],{"class":58},[48,872,484],{"class":235},[48,874,875],{"class":58},"\u002Fenviron",[48,877,878],{"class":235}," |",[48,880,881],{"class":54}," tr",[48,883,884],{"class":235}," '",[48,886,887],{"class":58},"\\0",[48,889,890],{"class":235},"'",[48,892,884],{"class":235},[48,894,895],{"class":58},"\\n",[48,897,898],{"class":235},"'\n",[10,900,902],{"id":901},"_5-restart-policies","5. Restart policies",[447,904,905,917],{},[450,906,907],{},[453,908,909,914],{},[456,910,911],{},[19,912,913],{},"Restart=",[456,915,916],{},"Restarts when",[463,918,919,929,939,949],{},[453,920,921,926],{},[468,922,923],{},[19,924,925],{},"no",[468,927,928],{},"Never (default)",[453,930,931,936],{},[468,932,933],{},[19,934,935],{},"on-failure",[468,937,938],{},"Non-zero exit code, signal, timeout",[453,940,941,946],{},[468,942,943],{},[19,944,945],{},"on-abnormal",[468,947,948],{},"Signal or timeout (not exit codes)",[453,950,951,956],{},[468,952,953],{},[19,954,955],{},"always",[468,957,958],{},"On every exit, including clean ones",[15,960,961],{},"To keep a constantly crashing service from looping forever:",[39,963,965],{"className":221,"code":964,"language":223,"meta":44,"style":44},"[Unit]\nStartLimitIntervalSec=60\nStartLimitBurst=5\n",[19,966,967,971,981],{"__ignoreMap":44},[48,968,969],{"class":50,"line":51},[48,970,236],{"class":235},[48,972,973,976,978],{"class":50,"line":72},[48,974,975],{"class":241},"StartLimitIntervalSec",[48,977,245],{"class":235},[48,979,980],{"class":248},"60\n",[48,982,983,986,988],{"class":50,"line":87},[48,984,985],{"class":241},"StartLimitBurst",[48,987,245],{"class":235},[48,989,351],{"class":248},[15,991,992,993,996,997,33],{},"With more than 5 start attempts within 60 seconds, systemd marks the service ",[19,994,995],{},"failed"," and stops trying. Reset the counter with ",[19,998,999],{},"systemctl reset-failed myapp",[10,1001,1003],{"id":1002},"_6-logs-with-journalctl","6. Logs with journalctl",[15,1005,1006],{},"The service's stdout\u002Fstderr goes to the journal automatically:",[39,1008,1010],{"className":41,"code":1009,"language":43,"meta":44,"style":44},"journalctl -u myapp                  # all logs\njournalctl -u myapp -f               # follow\njournalctl -u myapp -n 200           # last 200 lines\njournalctl -u myapp --since \"1 hour ago\"\njournalctl -u myapp --since today\njournalctl -u myapp --since \"2026-10-30 09:00\" --until \"2026-10-30 10:00\"\njournalctl -u myapp -p err           # error and above only\njournalctl -u myapp -b               # since the last boot\njournalctl -u myapp -o cat           # plain output without timestamps\njournalctl --disk-usage\nsudo journalctl --vacuum-time=14d    # delete logs older than 14 days\n",[19,1011,1012,1025,1039,1057,1077,1090,1118,1134,1148,1164,1171],{"__ignoreMap":44},[48,1013,1014,1017,1020,1022],{"class":50,"line":51},[48,1015,1016],{"class":54},"journalctl",[48,1018,1019],{"class":58}," -u",[48,1021,65],{"class":58},[48,1023,1024],{"class":68},"                  # all logs\n",[48,1026,1027,1029,1031,1033,1036],{"class":50,"line":72},[48,1028,1016],{"class":54},[48,1030,1019],{"class":58},[48,1032,65],{"class":58},[48,1034,1035],{"class":58}," -f",[48,1037,1038],{"class":68},"               # follow\n",[48,1040,1041,1043,1045,1047,1050,1054],{"class":50,"line":87},[48,1042,1016],{"class":54},[48,1044,1019],{"class":58},[48,1046,65],{"class":58},[48,1048,1049],{"class":58}," -n",[48,1051,1053],{"class":1052},"sbssI"," 200",[48,1055,1056],{"class":68},"           # last 200 lines\n",[48,1058,1059,1061,1063,1065,1068,1071,1074],{"class":50,"line":102},[48,1060,1016],{"class":54},[48,1062,1019],{"class":58},[48,1064,65],{"class":58},[48,1066,1067],{"class":58}," --since",[48,1069,1070],{"class":235}," \"",[48,1072,1073],{"class":58},"1 hour ago",[48,1075,1076],{"class":235},"\"\n",[48,1078,1079,1081,1083,1085,1087],{"class":50,"line":117},[48,1080,1016],{"class":54},[48,1082,1019],{"class":58},[48,1084,65],{"class":58},[48,1086,1067],{"class":58},[48,1088,1089],{"class":58}," today\n",[48,1091,1092,1094,1096,1098,1100,1102,1105,1108,1111,1113,1116],{"class":50,"line":132},[48,1093,1016],{"class":54},[48,1095,1019],{"class":58},[48,1097,65],{"class":58},[48,1099,1067],{"class":58},[48,1101,1070],{"class":235},[48,1103,1104],{"class":58},"2026-10-30 09:00",[48,1106,1107],{"class":235},"\"",[48,1109,1110],{"class":58}," --until",[48,1112,1070],{"class":235},[48,1114,1115],{"class":58},"2026-10-30 10:00",[48,1117,1076],{"class":235},[48,1119,1120,1122,1124,1126,1128,1131],{"class":50,"line":149},[48,1121,1016],{"class":54},[48,1123,1019],{"class":58},[48,1125,65],{"class":58},[48,1127,862],{"class":58},[48,1129,1130],{"class":58}," err",[48,1132,1133],{"class":68},"           # error and above only\n",[48,1135,1136,1138,1140,1142,1145],{"class":50,"line":162},[48,1137,1016],{"class":54},[48,1139,1019],{"class":58},[48,1141,65],{"class":58},[48,1143,1144],{"class":58}," -b",[48,1146,1147],{"class":68},"               # since the last boot\n",[48,1149,1150,1152,1154,1156,1159,1161],{"class":50,"line":176},[48,1151,1016],{"class":54},[48,1153,1019],{"class":58},[48,1155,65],{"class":58},[48,1157,1158],{"class":58}," -o",[48,1160,849],{"class":58},[48,1162,1163],{"class":68},"           # plain output without timestamps\n",[48,1165,1166,1168],{"class":50,"line":186},[48,1167,1016],{"class":54},[48,1169,1170],{"class":58}," --disk-usage\n",[48,1172,1173,1175,1178,1181],{"class":50,"line":200},[48,1174,55],{"class":54},[48,1176,1177],{"class":58}," journalctl",[48,1179,1180],{"class":58}," --vacuum-time=14d",[48,1182,1183],{"class":68},"    # delete logs older than 14 days\n",[15,1185,1186,1187,1190,1191,1194,1195,33],{},"To cap the journal's disk usage permanently, set ",[19,1188,1189],{},"SystemMaxUse=500M"," in ",[19,1192,1193],{},"\u002Fetc\u002Fsystemd\u002Fjournald.conf"," and run ",[19,1196,1197],{},"sudo systemctl restart systemd-journald",[10,1199,1201],{"id":1200},"_7-simple-hardening","7. Simple hardening",[15,1203,1204],{},"systemd lets you restrict a process with a few lines:",[39,1206,1208],{"className":221,"code":1207,"language":223,"meta":44,"style":44},"[Service]\nNoNewPrivileges=true\nPrivateTmp=true\nProtectSystem=strict\nProtectHome=true\nReadWritePaths=\u002Fopt\u002Fmyapp\u002Fdata \u002Fvar\u002Flog\u002Fmyapp\n",[19,1209,1210,1214,1224,1233,1243,1252],{"__ignoreMap":44},[48,1211,1212],{"class":50,"line":51},[48,1213,279],{"class":235},[48,1215,1216,1219,1221],{"class":50,"line":72},[48,1217,1218],{"class":241},"NoNewPrivileges",[48,1220,245],{"class":235},[48,1222,1223],{"class":248},"true\n",[48,1225,1226,1229,1231],{"class":50,"line":87},[48,1227,1228],{"class":241},"PrivateTmp",[48,1230,245],{"class":235},[48,1232,1223],{"class":248},[48,1234,1235,1238,1240],{"class":50,"line":102},[48,1236,1237],{"class":241},"ProtectSystem",[48,1239,245],{"class":235},[48,1241,1242],{"class":248},"strict\n",[48,1244,1245,1248,1250],{"class":50,"line":117},[48,1246,1247],{"class":241},"ProtectHome",[48,1249,245],{"class":235},[48,1251,1223],{"class":248},[48,1253,1254,1257,1259],{"class":50,"line":132},[48,1255,1256],{"class":241},"ReadWritePaths",[48,1258,245],{"class":235},[48,1260,1261],{"class":248},"\u002Fopt\u002Fmyapp\u002Fdata \u002Fvar\u002Flog\u002Fmyapp\n",[15,1263,1264,1267,1268,1270,1271,1274],{},[19,1265,1266],{},"ProtectSystem=strict"," makes the whole file system read-only; open up the folders the app needs to write with ",[19,1269,1256],{},". Use ",[19,1272,1273],{},"systemd-analyze security myapp"," to see how well protected a service is.",[10,1276,1278],{"id":1277},"_8-scheduled-jobs-with-timers","8. Scheduled jobs with timers",[15,1280,1281],{},"systemd timers are an alternative to cron; logs land in the journal and missed runs can be caught up. A backup running every night at 02:00:",[39,1283,1285],{"className":221,"code":1284,"language":223,"meta":44,"style":44},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.service\n[Unit]\nDescription=Nightly database backup\n\n[Service]\nType=oneshot\nUser=postgres\nExecStart=\u002Fusr\u002Flocal\u002Fbin\u002Fbackup.sh\n",[19,1286,1287,1292,1296,1305,1309,1313,1322,1331],{"__ignoreMap":44},[48,1288,1289],{"class":50,"line":51},[48,1290,1291],{"class":68},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.service\n",[48,1293,1294],{"class":50,"line":72},[48,1295,236],{"class":235},[48,1297,1298,1300,1302],{"class":50,"line":87},[48,1299,242],{"class":241},[48,1301,245],{"class":235},[48,1303,1304],{"class":248},"Nightly database backup\n",[48,1306,1307],{"class":50,"line":102},[48,1308,274],{"emptyLinePlaceholder":273},[48,1310,1311],{"class":50,"line":117},[48,1312,279],{"class":235},[48,1314,1315,1317,1319],{"class":50,"line":132},[48,1316,284],{"class":241},[48,1318,245],{"class":235},[48,1320,1321],{"class":248},"oneshot\n",[48,1323,1324,1326,1328],{"class":50,"line":149},[48,1325,294],{"class":241},[48,1327,245],{"class":235},[48,1329,1330],{"class":248},"postgres\n",[48,1332,1333,1335,1337],{"class":50,"line":162},[48,1334,324],{"class":241},[48,1336,245],{"class":235},[48,1338,1339],{"class":248},"\u002Fusr\u002Flocal\u002Fbin\u002Fbackup.sh\n",[39,1341,1343],{"className":221,"code":1342,"language":223,"meta":44,"style":44},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.timer\n[Unit]\nDescription=Run backup nightly\n\n[Timer]\nOnCalendar=*-*-* 02:00:00\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n",[19,1344,1345,1350,1354,1363,1367,1372,1382,1391,1395,1399],{"__ignoreMap":44},[48,1346,1347],{"class":50,"line":51},[48,1348,1349],{"class":68},"# \u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.timer\n",[48,1351,1352],{"class":50,"line":72},[48,1353,236],{"class":235},[48,1355,1356,1358,1360],{"class":50,"line":87},[48,1357,242],{"class":241},[48,1359,245],{"class":235},[48,1361,1362],{"class":248},"Run backup nightly\n",[48,1364,1365],{"class":50,"line":102},[48,1366,274],{"emptyLinePlaceholder":273},[48,1368,1369],{"class":50,"line":117},[48,1370,1371],{"class":235},"[Timer]\n",[48,1373,1374,1377,1379],{"class":50,"line":132},[48,1375,1376],{"class":241},"OnCalendar",[48,1378,245],{"class":235},[48,1380,1381],{"class":248},"*-*-* 02:00:00\n",[48,1383,1384,1387,1389],{"class":50,"line":149},[48,1385,1386],{"class":241},"Persistent",[48,1388,245],{"class":235},[48,1390,1223],{"class":248},[48,1392,1393],{"class":50,"line":162},[48,1394,274],{"emptyLinePlaceholder":273},[48,1396,1397],{"class":50,"line":176},[48,1398,373],{"class":235},[48,1400,1401,1403,1405],{"class":50,"line":186},[48,1402,379],{"class":241},[48,1404,245],{"class":235},[48,1406,1407],{"class":248},"timers.target\n",[39,1409,1411],{"className":41,"code":1410,"language":43,"meta":44,"style":44},"sudo systemctl daemon-reload\nsudo systemctl enable --now backup.timer\nsystemctl list-timers\nsystemd-analyze calendar \"*-*-* 02:00:00\"   # next run time of the expression\n",[19,1412,1413,1422,1435,1442],{"__ignoreMap":44},[48,1414,1415,1417,1419],{"class":50,"line":51},[48,1416,55],{"class":54},[48,1418,59],{"class":58},[48,1420,1421],{"class":58}," daemon-reload\n",[48,1423,1424,1426,1428,1430,1432],{"class":50,"line":72},[48,1425,55],{"class":54},[48,1427,59],{"class":58},[48,1429,124],{"class":58},[48,1431,141],{"class":58},[48,1433,1434],{"class":58}," backup.timer\n",[48,1436,1437,1439],{"class":50,"line":87},[48,1438,165],{"class":54},[48,1440,1441],{"class":58}," list-timers\n",[48,1443,1444,1447,1450,1452,1455,1457],{"class":50,"line":102},[48,1445,1446],{"class":54},"systemd-analyze",[48,1448,1449],{"class":58}," calendar",[48,1451,1070],{"class":235},[48,1453,1454],{"class":58},"*-*-* 02:00:00",[48,1456,1107],{"class":235},[48,1458,1459],{"class":68},"   # next run time of the expression\n",[15,1461,1462,1465],{},[19,1463,1464],{},"Persistent=true"," runs the job right after boot if the server was off at 02:00.",[10,1467,1469],{"id":1468},"_9-troubleshooting","9. Troubleshooting",[447,1471,1472,1482],{},[450,1473,1474],{},[453,1475,1476,1479],{},[456,1477,1478],{},"Situation",[456,1480,1481],{},"Where to look",[463,1483,1484,1497,1511,1521,1538,1553],{},[453,1485,1486,1491],{},[468,1487,1488],{},[19,1489,1490],{},"status=203\u002FEXEC",[468,1492,1493,1494,1496],{},"Wrong ",[19,1495,324],{}," path or the file isn't executable; use an absolute path",[453,1498,1499,1504],{},[468,1500,1501],{},[19,1502,1503],{},"status=217\u002FUSER",[468,1505,1506,1507,1510],{},"The user given in ",[19,1508,1509],{},"User="," doesn't exist",[453,1512,1513,1516],{},[468,1514,1515],{},"Changes have no effect",[468,1517,1518,1520],{},[19,1519,527],{}," was forgotten",[453,1522,1523,1526],{},[468,1524,1525],{},"Works in a terminal, not as a service",[468,1527,1528,1529,1532,1533,1535,1536],{},"Different ",[19,1530,1531],{},"PATH"," and environment; set ",[19,1534,313],{}," and ",[19,1537,613],{},[453,1539,1540,1543],{},[468,1541,1542],{},"The service keeps restarting",[468,1544,1545,1546,1549,1550],{},"Read the error with ",[19,1547,1548],{},"journalctl -u myapp -n 100","; reset with ",[19,1551,1552],{},"systemctl reset-failed",[453,1554,1555,1558],{},[468,1556,1557],{},"Is the unit file valid?",[468,1559,1560],{},[19,1561,1562],{},"systemd-analyze verify \u002Fetc\u002Fsystemd\u002Fsystem\u002Fmyapp.service",[10,1564,1566],{"id":1565},"conclusion","Conclusion",[15,1568,1569,1570,1573,1574,1577,1578,1581],{},"Writing a systemd service means a 10–15 line unit file. In return you get automatic restarts, start at boot, centralized logs and process isolation without installing anything else. Three commands to remember: ",[19,1571,1572],{},"daemon-reload"," after a change, ",[19,1575,1576],{},"systemctl status"," for the state, ",[19,1579,1580],{},"journalctl -u"," for the reason.",[1583,1584,1585],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":44,"searchDepth":72,"depth":72,"links":1587},[1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598],{"id":12,"depth":72,"text":13},{"id":36,"depth":72,"text":37},{"id":210,"depth":72,"text":211},{"id":531,"depth":72,"text":532},{"id":766,"depth":72,"text":767},{"id":901,"depth":72,"text":902},{"id":1002,"depth":72,"text":1003},{"id":1200,"depth":72,"text":1201},{"id":1277,"depth":72,"text":1278},{"id":1468,"depth":72,"text":1469},{"id":1565,"depth":72,"text":1566},"2026-10-30","Running an app as a systemd service — unit files, restart policies, environment variables, reading logs with journalctl, and scheduled jobs with timers.","md",{},"\u002Fen\u002Fblog\u002Fsystemd-service-guide",{"title":5,"description":1600},"en\u002Fblog\u002Fsystemd-service-guide",[1607,28,1608,1609],"linux","devops","guide","\u002Fblog\u002Fsystemd-service-guide","jftnppm7ndzQTEyKkuJh4VC190p0iZuA_7QYnJ87-HA",[1613,1621,1628],{"path":1614,"title":1615,"description":1616,"date":1617,"tags":1618},"\u002Fen\u002Fblog\u002Fgithub-actions-ci-cd","CI\u002FCD with GitHub Actions — Build, Test and Deploy to Your Server","A step-by-step GitHub Actions workflow that builds and tests on every push and deploys to your own server over SSH when changes land on main.","2026-10-29",[1619,1620,1608,1609],"github","ci-cd",{"path":1622,"title":1623,"description":1624,"date":1625,"tags":1626},"\u002Fen\u002Fblog\u002Fnginx-reverse-proxy-ssl","Setting Up an Nginx Reverse Proxy with Let's Encrypt SSL","Putting a Node.js or Spring Boot app behind Nginx, getting a free SSL certificate with Certbot, and setting up automatic renewal.","2026-10-28",[1627,1607,1608,1609],"nginx",{"path":1629,"title":1630,"description":1631,"date":1632,"tags":1633},"\u002Fen\u002Fblog\u002Fdocker-compose-guide","Running Multiple Services with Docker Compose","Bringing up an app, a database and a cache from a single compose.yaml — services, networks, volumes, healthchecks, env files and everyday commands.","2026-10-27",[1634,1608,1609],"docker",1793321447328]