[{"data":1,"prerenderedAt":1679},["ShallowReactive",2],{"blog-posts":3},[4,534],{"id":5,"title":6,"body":7,"date":515,"description":516,"extension":517,"image":518,"imageAlt":519,"imageCaption":520,"imageHeight":521,"imageWidth":522,"meta":523,"navigation":205,"path":524,"published":205,"readingTime":152,"seo":525,"stem":526,"tags":527,"updated":532,"__hash__":533},"blog\u002Fblog\u002Fmonitor-self-hosted-services.md","How I monitor all my services with Uptime Kuma — get notified via phone in seconds",{"type":8,"value":9,"toc":506},"minimark",[10,14,31,36,49,52,56,59,74,81,85,93,96,355,358,361,365,368,407,416,419,423,430,433,436,463,466,470,476,482,488,490,494,497,502],[11,12,13],"p",{},"Running services and keeping them running are two different things. The second part is what catches most people off guard, especially when you're the only one responsible.",[11,15,16,17,24,25,30],{},"This article covers how I set up ",[18,19,23],"a",{"href":20,"rel":21},"https:\u002F\u002Fgithub.com\u002Flouislam\u002Fuptime-kuma",[22],"nofollow","Uptime Kuma"," together with ",[18,26,29],{"href":27,"rel":28},"https:\u002F\u002Fntfy.sh",[22],"ntfy"," to monitor all my services and get phone notifications within seconds when something goes down.",[32,33,35],"h2",{"id":34},"prerequisites","Prerequisites",[37,38,39,43,46],"ul",{},[40,41,42],"li",{},"A server with Windows or Linux running 24\u002F7 (a Raspberry Pi works fine)",[40,44,45],{},"1 vCPU, 512 MB RAM, 1 GB storage",[40,47,48],{},"sudo access and Docker Compose installed",[50,51],"hr",{},[32,53,55],{"id":54},"why-i-stopped-using-cloud-monitoring-tools","Why I stopped using cloud monitoring tools",[11,57,58],{},"When I first started self-hosting, I had no idea when things broke. A Docker container silently failing after a restart, a website update that took the whole site down — I'd only find out hours later, usually from someone else.",[11,60,61,62,67,68,73],{},"My first attempt at a fix was ",[18,63,66],{"href":64,"rel":65},"https:\u002F\u002Fuptimerobot.com\u002F",[22],"UptimeRobot",", then ",[18,69,72],{"href":70,"rel":71},"https:\u002F\u002Fbetterstack.com\u002F",[22],"Betterstack",". Both work fine, but I kept hitting the free tier ceiling: marketing emails, dashboards clearly designed for ops teams, way more features than I needed. I just wanted to know if my stuff was up.",[11,75,76,77,80],{},"Then I was browsing GitHub one day and noticed a friend had starred a project called ",[18,78,23],{"href":20,"rel":79},[22],". I clicked through, saw the screenshots, and that was it. Clean UI, open source, self-hosted. Over 80k stars means the project works and is actively maintained. I was sold before I even read the docs.",[32,82,84],{"id":83},"getting-it-running","Getting it running",[11,86,87,88,92],{},"I run everything with Docker, and Uptime Kuma already ships a ",[89,90,91],"code",{},"docker-compose.yaml"," in the repository.",[11,94,95],{},"Below is the compose file I use. It includes both Uptime Kuma and ntfy — the push notification service I'll cover in the next section:",[97,98,103],"pre",{"className":99,"code":100,"language":101,"meta":102,"style":102},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","services:\n  uptime-kuma:\n    image: louislam\u002Fuptime-kuma:2\n    container_name: uptime-kuma\n    volumes:\n      - uptime-kuma-data:\u002Fapp\u002Fdata\n    ports:\n      - \"127.0.0.1:4000:3001\"\n    restart: unless-stopped\n\n  ntfy:\n    image: binwiederhier\u002Fntfy\n    container_name: ntfy\n    command:\n      - serve\n    environment:\n      - NTFY_BASE_URL=https:\u002F\u002Fntfy.your-domain.dev\n      - NTFY_UPSTREAM_BASE_URL=https:\u002F\u002Fntfy.sh\n      - NTFY_BEHIND_PROXY=true\n      - TZ=UTC\n    user: \"1000:1000\"\n    ports:\n      - \"127.0.0.1:8080:80\"\n    restart: unless-stopped\n\nvolumes:\n    uptime-kuma-data:\n","yaml","",[89,104,105,118,126,139,150,158,167,175,189,200,207,215,225,235,243,251,259,267,275,283,291,306,313,325,334,339,347],{"__ignoreMap":102},[106,107,110,114],"span",{"class":108,"line":109},"line",1,[106,111,113],{"class":112},"swJcz","services",[106,115,117],{"class":116},"sMK4o",":\n",[106,119,121,124],{"class":108,"line":120},2,[106,122,123],{"class":112},"  uptime-kuma",[106,125,117],{"class":116},[106,127,129,132,135],{"class":108,"line":128},3,[106,130,131],{"class":112},"    image",[106,133,134],{"class":116},":",[106,136,138],{"class":137},"sfazB"," louislam\u002Fuptime-kuma:2\n",[106,140,142,145,147],{"class":108,"line":141},4,[106,143,144],{"class":112},"    container_name",[106,146,134],{"class":116},[106,148,149],{"class":137}," uptime-kuma\n",[106,151,153,156],{"class":108,"line":152},5,[106,154,155],{"class":112},"    volumes",[106,157,117],{"class":116},[106,159,161,164],{"class":108,"line":160},6,[106,162,163],{"class":116},"      -",[106,165,166],{"class":137}," uptime-kuma-data:\u002Fapp\u002Fdata\n",[106,168,170,173],{"class":108,"line":169},7,[106,171,172],{"class":112},"    ports",[106,174,117],{"class":116},[106,176,178,180,183,186],{"class":108,"line":177},8,[106,179,163],{"class":116},[106,181,182],{"class":116}," \"",[106,184,185],{"class":137},"127.0.0.1:4000:3001",[106,187,188],{"class":116},"\"\n",[106,190,192,195,197],{"class":108,"line":191},9,[106,193,194],{"class":112},"    restart",[106,196,134],{"class":116},[106,198,199],{"class":137}," unless-stopped\n",[106,201,203],{"class":108,"line":202},10,[106,204,206],{"emptyLinePlaceholder":205},true,"\n",[106,208,210,213],{"class":108,"line":209},11,[106,211,212],{"class":112},"  ntfy",[106,214,117],{"class":116},[106,216,218,220,222],{"class":108,"line":217},12,[106,219,131],{"class":112},[106,221,134],{"class":116},[106,223,224],{"class":137}," binwiederhier\u002Fntfy\n",[106,226,228,230,232],{"class":108,"line":227},13,[106,229,144],{"class":112},[106,231,134],{"class":116},[106,233,234],{"class":137}," ntfy\n",[106,236,238,241],{"class":108,"line":237},14,[106,239,240],{"class":112},"    command",[106,242,117],{"class":116},[106,244,246,248],{"class":108,"line":245},15,[106,247,163],{"class":116},[106,249,250],{"class":137}," serve\n",[106,252,254,257],{"class":108,"line":253},16,[106,255,256],{"class":112},"    environment",[106,258,117],{"class":116},[106,260,262,264],{"class":108,"line":261},17,[106,263,163],{"class":116},[106,265,266],{"class":137}," NTFY_BASE_URL=https:\u002F\u002Fntfy.your-domain.dev\n",[106,268,270,272],{"class":108,"line":269},18,[106,271,163],{"class":116},[106,273,274],{"class":137}," NTFY_UPSTREAM_BASE_URL=https:\u002F\u002Fntfy.sh\n",[106,276,278,280],{"class":108,"line":277},19,[106,279,163],{"class":116},[106,281,282],{"class":137}," NTFY_BEHIND_PROXY=true\n",[106,284,286,288],{"class":108,"line":285},20,[106,287,163],{"class":116},[106,289,290],{"class":137}," TZ=UTC\n",[106,292,294,297,299,301,304],{"class":108,"line":293},21,[106,295,296],{"class":112},"    user",[106,298,134],{"class":116},[106,300,182],{"class":116},[106,302,303],{"class":137},"1000:1000",[106,305,188],{"class":116},[106,307,309,311],{"class":108,"line":308},22,[106,310,172],{"class":112},[106,312,117],{"class":116},[106,314,316,318,320,323],{"class":108,"line":315},23,[106,317,163],{"class":116},[106,319,182],{"class":116},[106,321,322],{"class":137},"127.0.0.1:8080:80",[106,324,188],{"class":116},[106,326,328,330,332],{"class":108,"line":327},24,[106,329,194],{"class":112},[106,331,134],{"class":116},[106,333,199],{"class":137},[106,335,337],{"class":108,"line":336},25,[106,338,206],{"emptyLinePlaceholder":205},[106,340,342,345],{"class":108,"line":341},26,[106,343,344],{"class":112},"volumes",[106,346,117],{"class":116},[106,348,350,353],{"class":108,"line":349},27,[106,351,352],{"class":112},"    uptime-kuma-data",[106,354,117],{"class":116},[11,356,357],{},"On first launch, Uptime Kuma asks which database to use. I always pick SQLite — it's lightweight, needs no extra setup, and works perfectly for personal use. A full database setup only makes sense if you want to query your data from Grafana, run multiple instances, or need proper dump\u002Frestore workflows.",[11,359,360],{},"After that, create an admin account so nobody else can touch your config. That's the whole setup.",[32,362,364],{"id":363},"configuring-monitors","Configuring monitors",[11,366,367],{},"Adding a monitor takes about 30 seconds. Pick a name, choose a type, set the check interval. I currently use three types:",[37,369,370,389,395],{},[40,371,372,376,377,382,383,388],{},[373,374,375],"strong",{},"HTTP(S)",": checks that a URL responds successfully. I use this for my websites (",[18,378,381],{"href":379,"rel":380},"https:\u002F\u002Fdanobe.dev",[22],"danobe.dev"," and ",[18,384,387],{"href":385,"rel":386},"https:\u002F\u002Fcarpf.it",[22],"carpf.it",") and for any service that exposes a web interface.",[40,390,391,394],{},[373,392,393],{},"Ping",": sends a ping to an external server. I target Google's servers — if this fails, my internet connection is down, not the service itself.",[40,396,397,400,401,406],{},[373,398,399],{},"Push",": the service sends a heartbeat to Uptime Kuma on a fixed schedule. I use this for ",[18,402,405],{"href":403,"rel":404},"https:\u002F\u002Fgithub.com\u002Fhenrygd\u002Fbeszel",[22],"Beszel",", which monitors my server's hardware resources. When the heartbeat stops arriving, Uptime Kuma triggers an alert.",[11,408,409],{},[410,411],"img",{"alt":412,"height":413,"src":414,"width":415},"All my current monitored services by uptime kuma",968,"\u002Fimages\u002Fblog\u002Fmonitor-self-hosted-services\u002Fmy-uptime-kuma-services.webp",1668,[11,417,418],{},"My current setup looks like the screenshot above — every monitor shows its status, response time history, and uptime percentage at a glance.",[32,420,422],{"id":421},"getting-notified-on-your-phone-with-ntfy","Getting notified on your phone with ntfy",[11,424,425,426,429],{},"Knowing something is broken is only useful if you find out in time. I use ",[18,427,29],{"href":27,"rel":428},[22]," for alerts — it has apps for iOS and Android, and a PWA for macOS.",[11,431,432],{},"You can use the free hosted service at ntfy.sh, but I run my own instance for full control. It's already in the compose file above. Once it's running, connect it to Uptime Kuma through the notification settings — there's a built-in ntfy integration. From there, any failed check pushes a notification to my phone within seconds.",[11,434,435],{},"I also use ntfy in other places: backup scripts that notify me when a job starts and when it finishes. One curl command is all it takes:",[97,437,441],{"className":438,"code":439,"language":440,"meta":102,"style":102},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","curl -d \"Backup finished\" https:\u002F\u002Fntfy.your-domain.dev\u002FBackups\n","bash",[89,442,443],{"__ignoreMap":102},[106,444,445,449,452,454,457,460],{"class":108,"line":109},[106,446,448],{"class":447},"sBMFI","curl",[106,450,451],{"class":137}," -d",[106,453,182],{"class":116},[106,455,456],{"class":137},"Backup finished",[106,458,459],{"class":116},"\"",[106,461,462],{"class":137}," https:\u002F\u002Fntfy.your-domain.dev\u002FBackups\n",[11,464,465],{},"Drop that into any bash script or cron job. It's a small thing, but it removes a lot of \"wait, did that actually run?\" uncertainty.",[32,467,469],{"id":468},"a-few-more-features-worth-knowing","A few more features worth knowing",[11,471,472,475],{},[373,473,474],{},"Status page"," — Uptime Kuma can generate a public-facing status page you can link to from your website. Useful if you have clients or users who want to check whether something is down without contacting you. I haven't needed this yet for my setup, but it's a clean option to have.",[11,477,478,481],{},[373,479,480],{},"Maintenance windows"," — You can define scheduled windows where alerts are suppressed. Practical for planned reboots: without this, a server going offline for two minutes floods your phone with notifications.",[11,483,484,487],{},[373,485,486],{},"Docker container monitoring"," — It can also check whether specific containers are running, not just whether a URL responds. More precise for services where a web endpoint isn't the most reliable health signal.",[50,489],{},[32,491,493],{"id":492},"conclusion","Conclusion",[11,495,496],{},"If you run any services yourself, this combination is worth the hour it takes to set up. Uptime Kuma watches your infrastructure, ntfy gets the alerts to your phone, and you stop being the last person to find out when something breaks.",[498,499],"comment-section",{"section":500,"slug":501},"blog","monitor-self-hosted-services",[503,504,505],"style",{},"html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":102,"searchDepth":120,"depth":120,"links":507},[508,509,510,511,512,513,514],{"id":34,"depth":120,"text":35},{"id":54,"depth":120,"text":55},{"id":83,"depth":120,"text":84},{"id":363,"depth":120,"text":364},{"id":421,"depth":120,"text":422},{"id":468,"depth":120,"text":469},{"id":492,"depth":120,"text":493},"2026-05-23","How I monitor all my online services and websites with Uptime Kuma and get notified on my iPhone within seconds when something goes down.","md","\u002Fimages\u002Fblog\u002Fmonitor-self-hosted-services\u002Fheader.webp","A screenshot showing the software uptime kuma with some monitors configured","A dashboard showing the uptime of services",1024,1536,{},"\u002Fblog\u002Fmonitor-self-hosted-services",{"title":6,"description":516},"blog\u002Fmonitor-self-hosted-services",[528,529,530,531],"linux","server","homelab","monitoring",null,"V8cfSTnqQS9z5Vak3VWA1nsaWw02QO4V-jqlBbItvAE",{"id":535,"title":536,"body":537,"date":1666,"description":1667,"extension":517,"image":1668,"imageAlt":1669,"imageCaption":1670,"imageHeight":521,"imageWidth":522,"meta":1671,"navigation":205,"path":1672,"published":205,"readingTime":152,"seo":1673,"stem":1674,"tags":1675,"updated":532,"__hash__":1678},"blog\u002Fblog\u002Flinux-server-hardening.md","Linux Server Hardening: A Complete Security Guide",{"type":8,"value":538,"toc":1650},[539,542,544,555,557,560,564,567,621,626,735,738,772,775,777,781,784,788,794,848,851,855,858,864,908,915,919,922,948,951,970,973,1039,1045,1087,1093,1095,1099,1341,1343,1347,1350,1387,1393,1430,1433,1436,1461,1463,1467,1470,1488,1491,1525,1527,1531,1537,1604,1607,1620,1622,1625,1627,1631,1644,1647],[11,540,541],{},"Running a Linux server on the public internet means it will be targeted — constantly. Bots scan for open ports and weak credentials within minutes of a server going live. This guide covers the essential hardening steps to significantly reduce your attack surface and keep your Linux server as secure as possible.",[32,543,35],{"id":34},[37,545,546,549,552],{},[40,547,548],{},"A Linux server (e.g. Ubuntu, Debian, Fedora)",[40,550,551],{},"Root or sudo access",[40,553,554],{},"Basic familiarity with the terminal",[50,556],{},[558,559],"distro-selector",{},[32,561,563],{"id":562},"keep-the-system-updated","Keep the System Updated",[11,565,566],{},"The single most impactful thing you can do is keep packages up to date. Most breaches exploit known vulnerabilities that already have patches available.",[568,569,570,598],"pkg-switcher",{},[571,572,573],"template",{"v-slot:apt":102},[97,574,576],{"className":438,"code":575,"language":440,"meta":102,"style":102},"apt update && apt upgrade -y\n",[89,577,578],{"__ignoreMap":102},[106,579,580,583,586,589,592,595],{"class":108,"line":109},[106,581,582],{"class":447},"apt",[106,584,585],{"class":137}," update",[106,587,588],{"class":116}," &&",[106,590,591],{"class":447}," apt",[106,593,594],{"class":137}," upgrade",[106,596,597],{"class":137}," -y\n",[571,599,600],{"v-slot:dnf":102},[97,601,603],{"className":438,"code":602,"language":440,"meta":102,"style":102},"dnf update && dnf upgrade -y\n",[89,604,605],{"__ignoreMap":102},[106,606,607,610,612,614,617,619],{"class":108,"line":109},[106,608,609],{"class":447},"dnf",[106,611,585],{"class":137},[106,613,588],{"class":116},[106,615,616],{"class":447}," dnf",[106,618,594],{"class":137},[106,620,597],{"class":137},[622,623,625],"h3",{"id":624},"enable-automatic-security-updates","Enable Automatic Security Updates",[568,627,628,658],{},[571,629,630],{"v-slot:apt":102},[97,631,633],{"className":438,"code":632,"language":440,"meta":102,"style":102},"apt install unattended-upgrades -y\ndpkg-reconfigure --priority=low unattended-upgrades\n",[89,634,635,647],{"__ignoreMap":102},[106,636,637,639,642,645],{"class":108,"line":109},[106,638,582],{"class":447},[106,640,641],{"class":137}," install",[106,643,644],{"class":137}," unattended-upgrades",[106,646,597],{"class":137},[106,648,649,652,655],{"class":108,"line":120},[106,650,651],{"class":447},"dpkg-reconfigure",[106,653,654],{"class":137}," --priority=low",[106,656,657],{"class":137}," unattended-upgrades\n",[571,659,660],{"v-slot:dnf":102},[97,661,663],{"className":438,"code":662,"language":440,"meta":102,"style":102},"dnf install dnf-automatic -y\n\nsudo tee \u002Fetc\u002Fdnf\u002Fautomatic.conf \u003C\u003C 'EOF'\n[commands]\nupgrade_type = default\napply_updates = yes\nEOF\n\nsystemctl enable --now dnf-automatic-install.timer\n",[89,664,665,676,680,697,702,707,712,717,721],{"__ignoreMap":102},[106,666,667,669,671,674],{"class":108,"line":109},[106,668,609],{"class":447},[106,670,641],{"class":137},[106,672,673],{"class":137}," dnf-automatic",[106,675,597],{"class":137},[106,677,678],{"class":108,"line":120},[106,679,206],{"emptyLinePlaceholder":205},[106,681,682,685,688,691,694],{"class":108,"line":128},[106,683,684],{"class":447},"sudo",[106,686,687],{"class":137}," tee",[106,689,690],{"class":137}," \u002Fetc\u002Fdnf\u002Fautomatic.conf",[106,692,693],{"class":116}," \u003C\u003C",[106,695,696],{"class":116}," 'EOF'\n",[106,698,699],{"class":108,"line":141},[106,700,701],{"class":137},"[commands]\n",[106,703,704],{"class":108,"line":152},[106,705,706],{"class":137},"upgrade_type = default\n",[106,708,709],{"class":108,"line":160},[106,710,711],{"class":137},"apply_updates = yes\n",[106,713,714],{"class":108,"line":169},[106,715,716],{"class":116},"EOF\n",[106,718,719],{"class":108,"line":177},[106,720,206],{"emptyLinePlaceholder":205},[106,722,723,726,729,732],{"class":108,"line":191},[106,724,725],{"class":447},"systemctl",[106,727,728],{"class":137}," enable",[106,730,731],{"class":137}," --now",[106,733,734],{"class":137}," dnf-automatic-install.timer\n",[11,736,737],{},"This installs security patches automatically without requiring manual intervention.\nI also recommend rebooting your server once a week to apply kernel updates.\nYou can automate this with a cron job:",[97,739,741],{"className":438,"code":740,"language":440,"meta":102,"style":102},"crontab -e\n0 4 * * 1 \u002Fusr\u002Fsbin\u002Freboot\n",[89,742,743,751],{"__ignoreMap":102},[106,744,745,748],{"class":108,"line":109},[106,746,747],{"class":447},"crontab",[106,749,750],{"class":137}," -e\n",[106,752,753,756,760,764,766,769],{"class":108,"line":120},[106,754,755],{"class":447},"0",[106,757,759],{"class":758},"sbssI"," 4",[106,761,763],{"class":762},"sTEyZ"," *",[106,765,763],{"class":762},[106,767,768],{"class":758}," 1",[106,770,771],{"class":137}," \u002Fusr\u002Fsbin\u002Freboot\n",[11,773,774],{},"This reboots the server every Monday at 04:00 AM.",[50,776],{},[32,778,780],{"id":779},"secure-ssh-access","Secure SSH Access",[11,782,783],{},"SSH is the primary entry point for remote management — and the primary target for brute-force attacks.",[622,785,787],{"id":786},"disable-root-login-and-create-a-new-sudo-user","Disable Root Login And Create A New Sudo User",[11,789,790,791,793],{},"Never allow direct root login over SSH. Always use a regular user with ",[89,792,684],{},".\nIf you haven't already, create a dedicated admin user:",[568,795,796,824],{},[571,797,798],{"v-slot:apt":102},[97,799,801],{"className":438,"code":800,"language":440,"meta":102,"style":102},"adduser username\nusermod -aG sudo username\n",[89,802,803,811],{"__ignoreMap":102},[106,804,805,808],{"class":108,"line":109},[106,806,807],{"class":447},"adduser",[106,809,810],{"class":137}," username\n",[106,812,813,816,819,822],{"class":108,"line":120},[106,814,815],{"class":447},"usermod",[106,817,818],{"class":137}," -aG",[106,820,821],{"class":137}," sudo",[106,823,810],{"class":137},[571,825,826],{"v-slot:dnf":102},[97,827,829],{"className":438,"code":828,"language":440,"meta":102,"style":102},"adduser username\nusermod -aG wheel username\n",[89,830,831,837],{"__ignoreMap":102},[106,832,833,835],{"class":108,"line":109},[106,834,807],{"class":447},[106,836,810],{"class":137},[106,838,839,841,843,846],{"class":108,"line":120},[106,840,815],{"class":447},[106,842,818],{"class":137},[106,844,845],{"class":137}," wheel",[106,847,810],{"class":137},[11,849,850],{},"Switch to that user and verify sudo works before you close your root session.",[622,852,854],{"id":853},"change-default-port","Change default port",[11,856,857],{},"Changing the default port from 22 to something else reduces automated scan noise significantly.",[11,859,860,861,134],{},"Edit ",[89,862,863],{},"\u002Fetc\u002Fssh\u002Fsshd_config",[97,865,867],{"className":438,"code":866,"language":440,"meta":102,"style":102},"PermitRootLogin no\nPasswordAuthentication no\nPubkeyAuthentication yes\nAuthorizedKeysFile .ssh\u002Fauthorized_keys\nPort 222\n",[89,868,869,877,884,892,900],{"__ignoreMap":102},[106,870,871,874],{"class":108,"line":109},[106,872,873],{"class":447},"PermitRootLogin",[106,875,876],{"class":137}," no\n",[106,878,879,882],{"class":108,"line":120},[106,880,881],{"class":447},"PasswordAuthentication",[106,883,876],{"class":137},[106,885,886,889],{"class":108,"line":128},[106,887,888],{"class":447},"PubkeyAuthentication",[106,890,891],{"class":137}," yes\n",[106,893,894,897],{"class":108,"line":141},[106,895,896],{"class":447},"AuthorizedKeysFile",[106,898,899],{"class":137}," .ssh\u002Fauthorized_keys\n",[106,901,902,905],{"class":108,"line":152},[106,903,904],{"class":447},"Port",[106,906,907],{"class":758}," 222\n",[11,909,910,911,914],{},"Setting ",[89,912,913],{},"PasswordAuthentication no"," prevents all users from logging in with a password — only key-based authentication is accepted, which is significantly more secure.",[622,916,918],{"id":917},"use-ssh-keys-instead-of-passwords","Use SSH Keys Instead of Passwords",[11,920,921],{},"Generate a key pair on your local machine:",[97,923,925],{"className":438,"code":924,"language":440,"meta":102,"style":102},"ssh-keygen -t ed25519 -C \"your-email@example.com\"\n",[89,926,927],{"__ignoreMap":102},[106,928,929,932,935,938,941,943,946],{"class":108,"line":109},[106,930,931],{"class":447},"ssh-keygen",[106,933,934],{"class":137}," -t",[106,936,937],{"class":137}," ed25519",[106,939,940],{"class":137}," -C",[106,942,182],{"class":116},[106,944,945],{"class":137},"your-email@example.com",[106,947,188],{"class":116},[11,949,950],{},"Copy the public key to the server:",[97,952,954],{"className":438,"code":953,"language":440,"meta":102,"style":102},"ssh-copy-id -i ~\u002F.ssh\u002Fkey-name.pub username@your-server-ip\n",[89,955,956],{"__ignoreMap":102},[106,957,958,961,964,967],{"class":108,"line":109},[106,959,960],{"class":447},"ssh-copy-id",[106,962,963],{"class":137}," -i",[106,965,966],{"class":137}," ~\u002F.ssh\u002Fkey-name.pub",[106,968,969],{"class":137}," username@your-server-ip\n",[11,971,972],{},"Restart SSH after any config change:",[568,974,975,992],{},[571,976,977],{"v-slot:apt":102},[97,978,980],{"className":438,"code":979,"language":440,"meta":102,"style":102},"systemctl restart sshd\n",[89,981,982],{"__ignoreMap":102},[106,983,984,986,989],{"class":108,"line":109},[106,985,725],{"class":447},[106,987,988],{"class":137}," restart",[106,990,991],{"class":137}," sshd\n",[571,993,994],{"v-slot:dnf":102},[97,995,997],{"className":438,"code":996,"language":440,"meta":102,"style":102},"# SELinux must be told to allow SSH on the new port\nsudo semanage port -a -t ssh_port_t -p tcp 222\nsystemctl restart sshd\n",[89,998,999,1005,1031],{"__ignoreMap":102},[106,1000,1001],{"class":108,"line":109},[106,1002,1004],{"class":1003},"sHwdD","# SELinux must be told to allow SSH on the new port\n",[106,1006,1007,1009,1012,1015,1018,1020,1023,1026,1029],{"class":108,"line":120},[106,1008,684],{"class":447},[106,1010,1011],{"class":137}," semanage",[106,1013,1014],{"class":137}," port",[106,1016,1017],{"class":137}," -a",[106,1019,934],{"class":137},[106,1021,1022],{"class":137}," ssh_port_t",[106,1024,1025],{"class":137}," -p",[106,1027,1028],{"class":137}," tcp",[106,1030,907],{"class":758},[106,1032,1033,1035,1037],{"class":108,"line":128},[106,1034,725],{"class":447},[106,1036,988],{"class":137},[106,1038,991],{"class":137},[11,1040,860,1041,1044],{},[89,1042,1043],{},"~\u002F.ssh\u002Fconfig"," on your local machine to set up a quick alias for connecting to your server:",[97,1046,1048],{"className":438,"code":1047,"language":440,"meta":102,"style":102},"Host ServerName\nHostname ServerIPAddress\nPort 222\nUser username\nIdentityFile ~\u002F.ssh\u002Fserver-private-key\n",[89,1049,1050,1058,1066,1072,1079],{"__ignoreMap":102},[106,1051,1052,1055],{"class":108,"line":109},[106,1053,1054],{"class":447},"Host",[106,1056,1057],{"class":137}," ServerName\n",[106,1059,1060,1063],{"class":108,"line":120},[106,1061,1062],{"class":447},"Hostname",[106,1064,1065],{"class":137}," ServerIPAddress\n",[106,1067,1068,1070],{"class":108,"line":128},[106,1069,904],{"class":447},[106,1071,907],{"class":758},[106,1073,1074,1077],{"class":108,"line":141},[106,1075,1076],{"class":447},"User",[106,1078,810],{"class":137},[106,1080,1081,1084],{"class":108,"line":152},[106,1082,1083],{"class":447},"IdentityFile",[106,1085,1086],{"class":137}," ~\u002F.ssh\u002Fserver-private-key\n",[11,1088,1089,1090],{},"Now you can connect to your server like this: ",[89,1091,1092],{},"ssh ServerName",[50,1094],{},[32,1096,1098],{"id":1097},"configure-a-firewall","Configure a Firewall",[568,1100,1101,1232],{},[571,1102,1103],{"v-slot:apt":102},[97,1104,1106],{"className":438,"code":1105,"language":440,"meta":102,"style":102},"apt install ufw -y\n\n# Deny all incoming by default\nufw default deny incoming\nufw default allow outgoing\n\n# Allow SSH on the custom port\nufw allow 222\u002Ftcp\n\n# Allow web traffic if needed\nufw allow 80\u002Ftcp\nufw allow 443\u002Ftcp\nufw allow 443\u002Fudp # important for http protocol version 3\n\nufw enable\nufw status verbose\n",[89,1107,1108,1119,1123,1128,1142,1154,1158,1163,1172,1176,1181,1190,1199,1211,1215,1222],{"__ignoreMap":102},[106,1109,1110,1112,1114,1117],{"class":108,"line":109},[106,1111,582],{"class":447},[106,1113,641],{"class":137},[106,1115,1116],{"class":137}," ufw",[106,1118,597],{"class":137},[106,1120,1121],{"class":108,"line":120},[106,1122,206],{"emptyLinePlaceholder":205},[106,1124,1125],{"class":108,"line":128},[106,1126,1127],{"class":1003},"# Deny all incoming by default\n",[106,1129,1130,1133,1136,1139],{"class":108,"line":141},[106,1131,1132],{"class":447},"ufw",[106,1134,1135],{"class":137}," default",[106,1137,1138],{"class":137}," deny",[106,1140,1141],{"class":137}," incoming\n",[106,1143,1144,1146,1148,1151],{"class":108,"line":152},[106,1145,1132],{"class":447},[106,1147,1135],{"class":137},[106,1149,1150],{"class":137}," allow",[106,1152,1153],{"class":137}," outgoing\n",[106,1155,1156],{"class":108,"line":160},[106,1157,206],{"emptyLinePlaceholder":205},[106,1159,1160],{"class":108,"line":169},[106,1161,1162],{"class":1003},"# Allow SSH on the custom port\n",[106,1164,1165,1167,1169],{"class":108,"line":177},[106,1166,1132],{"class":447},[106,1168,1150],{"class":137},[106,1170,1171],{"class":137}," 222\u002Ftcp\n",[106,1173,1174],{"class":108,"line":191},[106,1175,206],{"emptyLinePlaceholder":205},[106,1177,1178],{"class":108,"line":202},[106,1179,1180],{"class":1003},"# Allow web traffic if needed\n",[106,1182,1183,1185,1187],{"class":108,"line":209},[106,1184,1132],{"class":447},[106,1186,1150],{"class":137},[106,1188,1189],{"class":137}," 80\u002Ftcp\n",[106,1191,1192,1194,1196],{"class":108,"line":217},[106,1193,1132],{"class":447},[106,1195,1150],{"class":137},[106,1197,1198],{"class":137}," 443\u002Ftcp\n",[106,1200,1201,1203,1205,1208],{"class":108,"line":227},[106,1202,1132],{"class":447},[106,1204,1150],{"class":137},[106,1206,1207],{"class":137}," 443\u002Fudp",[106,1209,1210],{"class":1003}," # important for http protocol version 3\n",[106,1212,1213],{"class":108,"line":237},[106,1214,206],{"emptyLinePlaceholder":205},[106,1216,1217,1219],{"class":108,"line":245},[106,1218,1132],{"class":447},[106,1220,1221],{"class":137}," enable\n",[106,1223,1224,1226,1229],{"class":108,"line":253},[106,1225,1132],{"class":447},[106,1227,1228],{"class":137}," status",[106,1230,1231],{"class":137}," verbose\n",[571,1233,1234],{"v-slot:dnf":102},[97,1235,1237],{"className":438,"code":1236,"language":440,"meta":102,"style":102},"# firewalld is installed by default on Fedora\u002FRHEL\nsystemctl enable --now firewalld\n\n# Allow SSH on the custom port\nfirewall-cmd --permanent --add-port=222\u002Ftcp\nfirewall-cmd --permanent --remove-service=ssh\n\n# Allow web traffic if needed\nfirewall-cmd --permanent --add-service=http\nfirewall-cmd --permanent --add-service=https\nsudo firewall-cmd --permanent --add-port=443\u002Fudp # important for http protocol version 3\n\nfirewall-cmd --reload\nfirewall-cmd --list-all\n",[89,1238,1239,1244,1255,1259,1263,1274,1283,1287,1291,1300,1309,1323,1327,1334],{"__ignoreMap":102},[106,1240,1241],{"class":108,"line":109},[106,1242,1243],{"class":1003},"# firewalld is installed by default on Fedora\u002FRHEL\n",[106,1245,1246,1248,1250,1252],{"class":108,"line":120},[106,1247,725],{"class":447},[106,1249,728],{"class":137},[106,1251,731],{"class":137},[106,1253,1254],{"class":137}," firewalld\n",[106,1256,1257],{"class":108,"line":128},[106,1258,206],{"emptyLinePlaceholder":205},[106,1260,1261],{"class":108,"line":141},[106,1262,1162],{"class":1003},[106,1264,1265,1268,1271],{"class":108,"line":152},[106,1266,1267],{"class":447},"firewall-cmd",[106,1269,1270],{"class":137}," --permanent",[106,1272,1273],{"class":137}," --add-port=222\u002Ftcp\n",[106,1275,1276,1278,1280],{"class":108,"line":160},[106,1277,1267],{"class":447},[106,1279,1270],{"class":137},[106,1281,1282],{"class":137}," --remove-service=ssh\n",[106,1284,1285],{"class":108,"line":169},[106,1286,206],{"emptyLinePlaceholder":205},[106,1288,1289],{"class":108,"line":177},[106,1290,1180],{"class":1003},[106,1292,1293,1295,1297],{"class":108,"line":191},[106,1294,1267],{"class":447},[106,1296,1270],{"class":137},[106,1298,1299],{"class":137}," --add-service=http\n",[106,1301,1302,1304,1306],{"class":108,"line":202},[106,1303,1267],{"class":447},[106,1305,1270],{"class":137},[106,1307,1308],{"class":137}," --add-service=https\n",[106,1310,1311,1313,1316,1318,1321],{"class":108,"line":209},[106,1312,684],{"class":447},[106,1314,1315],{"class":137}," firewall-cmd",[106,1317,1270],{"class":137},[106,1319,1320],{"class":137}," --add-port=443\u002Fudp",[106,1322,1210],{"class":1003},[106,1324,1325],{"class":108,"line":217},[106,1326,206],{"emptyLinePlaceholder":205},[106,1328,1329,1331],{"class":108,"line":227},[106,1330,1267],{"class":447},[106,1332,1333],{"class":137}," --reload\n",[106,1335,1336,1338],{"class":108,"line":237},[106,1337,1267],{"class":447},[106,1339,1340],{"class":137}," --list-all\n",[50,1342],{},[32,1344,1346],{"id":1345},"install-fail2ban","Install fail2ban",[11,1348,1349],{},"fail2ban monitors log files and automatically bans IPs that show signs of brute-force attacks.",[568,1351,1352,1370],{},[571,1353,1354],{"v-slot:apt":102},[97,1355,1357],{"className":438,"code":1356,"language":440,"meta":102,"style":102},"apt install fail2ban -y\n",[89,1358,1359],{"__ignoreMap":102},[106,1360,1361,1363,1365,1368],{"class":108,"line":109},[106,1362,582],{"class":447},[106,1364,641],{"class":137},[106,1366,1367],{"class":137}," fail2ban",[106,1369,597],{"class":137},[571,1371,1372],{"v-slot:dnf":102},[97,1373,1375],{"className":438,"code":1374,"language":440,"meta":102,"style":102},"dnf install fail2ban -y\n",[89,1376,1377],{"__ignoreMap":102},[106,1378,1379,1381,1383,1385],{"class":108,"line":109},[106,1380,609],{"class":447},[106,1382,641],{"class":137},[106,1384,1367],{"class":137},[106,1386,597],{"class":137},[11,1388,860,1389,1392],{},[89,1390,1391],{},"\u002Fetc\u002Ffail2ban\u002Fjail.local"," and create the following config:",[97,1394,1398],{"className":1395,"code":1396,"language":1397,"meta":102,"style":102},"language-ini shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[sshd]\nenabled  = true\nport     = 222\nmaxretry = 2\nbantime  = 30d\nfindtime = 24h\n","ini",[89,1399,1400,1405,1410,1415,1420,1425],{"__ignoreMap":102},[106,1401,1402],{"class":108,"line":109},[106,1403,1404],{},"[sshd]\n",[106,1406,1407],{"class":108,"line":120},[106,1408,1409],{},"enabled  = true\n",[106,1411,1412],{"class":108,"line":128},[106,1413,1414],{},"port     = 222\n",[106,1416,1417],{"class":108,"line":141},[106,1418,1419],{},"maxretry = 2\n",[106,1421,1422],{"class":108,"line":152},[106,1423,1424],{},"bantime  = 30d\n",[106,1426,1427],{"class":108,"line":160},[106,1428,1429],{},"findtime = 24h\n",[11,1431,1432],{},"These settings ban any IP after 2 failed login attempts within 24 hours, for 30 days.\nThis is an aggressive configuration, but with public key authentication you should never need more than one attempt anyway.",[11,1434,1435],{},"Start and enable fail2ban:",[97,1437,1439],{"className":438,"code":1438,"language":440,"meta":102,"style":102},"systemctl enable --now fail2ban\nfail2ban-client status sshd\n",[89,1440,1441,1452],{"__ignoreMap":102},[106,1442,1443,1445,1447,1449],{"class":108,"line":109},[106,1444,725],{"class":447},[106,1446,728],{"class":137},[106,1448,731],{"class":137},[106,1450,1451],{"class":137}," fail2ban\n",[106,1453,1454,1457,1459],{"class":108,"line":120},[106,1455,1456],{"class":447},"fail2ban-client",[106,1458,1228],{"class":137},[106,1460,991],{"class":137},[50,1462],{},[32,1464,1466],{"id":1465},"disable-unused-services","Disable Unused Services",[11,1468,1469],{},"Every running service is a potential attack surface. List what's running:",[97,1471,1473],{"className":438,"code":1472,"language":440,"meta":102,"style":102},"systemctl list-units --type=service --state=running\n",[89,1474,1475],{"__ignoreMap":102},[106,1476,1477,1479,1482,1485],{"class":108,"line":109},[106,1478,725],{"class":447},[106,1480,1481],{"class":137}," list-units",[106,1483,1484],{"class":137}," --type=service",[106,1486,1487],{"class":137}," --state=running\n",[11,1489,1490],{},"Disable anything you don't need, for example:",[97,1492,1494],{"className":438,"code":1493,"language":440,"meta":102,"style":102},"systemctl disable --now avahi-daemon # allowing devices on a local network to automatically discover each other \nsystemctl disable --now cups # printer service\n",[89,1495,1496,1511],{"__ignoreMap":102},[106,1497,1498,1500,1503,1505,1508],{"class":108,"line":109},[106,1499,725],{"class":447},[106,1501,1502],{"class":137}," disable",[106,1504,731],{"class":137},[106,1506,1507],{"class":137}," avahi-daemon",[106,1509,1510],{"class":1003}," # allowing devices on a local network to automatically discover each other \n",[106,1512,1513,1515,1517,1519,1522],{"class":108,"line":120},[106,1514,725],{"class":447},[106,1516,1502],{"class":137},[106,1518,731],{"class":137},[106,1520,1521],{"class":137}," cups",[106,1523,1524],{"class":1003}," # printer service\n",[50,1526],{},[32,1528,1530],{"id":1529},"harden-kernel-parameters-with-sysctl","Harden Kernel Parameters with sysctl",[11,1532,1533,1534,134],{},"Add the following to ",[89,1535,1536],{},"\u002Fetc\u002Fsysctl.d\u002F99-hardening.conf",[97,1538,1540],{"className":1395,"code":1539,"language":1397,"meta":102,"style":102},"# Prevent IP spoofing\nnet.ipv4.conf.all.rp_filter = 1\nnet.ipv4.conf.default.rp_filter = 1\n\n# Ignore ICMP redirects\nnet.ipv4.conf.all.accept_redirects = 0\nnet.ipv6.conf.all.accept_redirects = 0\n\n# Ignore broadcast pings\nnet.ipv4.icmp_echo_ignore_broadcasts = 1\n\n# Disable SYN flood attacks\nnet.ipv4.tcp_syncookies = 1\n",[89,1541,1542,1547,1552,1557,1561,1566,1571,1576,1580,1585,1590,1594,1599],{"__ignoreMap":102},[106,1543,1544],{"class":108,"line":109},[106,1545,1546],{},"# Prevent IP spoofing\n",[106,1548,1549],{"class":108,"line":120},[106,1550,1551],{},"net.ipv4.conf.all.rp_filter = 1\n",[106,1553,1554],{"class":108,"line":128},[106,1555,1556],{},"net.ipv4.conf.default.rp_filter = 1\n",[106,1558,1559],{"class":108,"line":141},[106,1560,206],{"emptyLinePlaceholder":205},[106,1562,1563],{"class":108,"line":152},[106,1564,1565],{},"# Ignore ICMP redirects\n",[106,1567,1568],{"class":108,"line":160},[106,1569,1570],{},"net.ipv4.conf.all.accept_redirects = 0\n",[106,1572,1573],{"class":108,"line":169},[106,1574,1575],{},"net.ipv6.conf.all.accept_redirects = 0\n",[106,1577,1578],{"class":108,"line":177},[106,1579,206],{"emptyLinePlaceholder":205},[106,1581,1582],{"class":108,"line":191},[106,1583,1584],{},"# Ignore broadcast pings\n",[106,1586,1587],{"class":108,"line":202},[106,1588,1589],{},"net.ipv4.icmp_echo_ignore_broadcasts = 1\n",[106,1591,1592],{"class":108,"line":209},[106,1593,206],{"emptyLinePlaceholder":205},[106,1595,1596],{"class":108,"line":217},[106,1597,1598],{},"# Disable SYN flood attacks\n",[106,1600,1601],{"class":108,"line":227},[106,1602,1603],{},"net.ipv4.tcp_syncookies = 1\n",[11,1605,1606],{},"Apply the changes:",[97,1608,1610],{"className":438,"code":1609,"language":440,"meta":102,"style":102},"sysctl --system\n",[89,1611,1612],{"__ignoreMap":102},[106,1613,1614,1617],{"class":108,"line":109},[106,1615,1616],{"class":447},"sysctl",[106,1618,1619],{"class":137}," --system\n",[50,1621],{},[11,1623,1624],{},"Security is not a one-time task. Revisit your server configuration regularly, monitor your logs, and stay informed about newly discovered vulnerabilities in the software you run.",[50,1626],{},[32,1628,1630],{"id":1629},"bonus-monitoring-and-alerting","Bonus – Monitoring and Alerting",[11,1632,1633,1634,1637,1638],{},"One bonus tip: install a monitoring tool like ",[18,1635,405],{"href":403,"rel":1636},[22],".\nIt's clean, well-designed, and I was immediately impressed by how easy it is to set up.\nYou can monitor your server's performance and configure alerts for when something looks off.\nI've set up alerts via ntfy, so I get a push notification directly on my iPhone whenever something like high CPU usage persists for an extended period or similar issues occur.\nBeszel also lets you monitor the status and performance of your Docker containers.\nIt runs on all major platforms — Windows, Linux, and macOS. If you want to step up your monitoring game, give it a shot.\nI'd wholeheartedly recommend it to any fellow IT professional. Here's a screenshot of the UI:\n",[410,1639],{"alt":1640,"height":1641,"src":1642,"width":1643},"Linux Server Hardening – A terminal showing active firewall and fail2ban protection",2551,"\u002Fimages\u002Fblog\u002Flinux-server-hardening\u002Fbeszel.webp",1422,[498,1645],{"section":500,"slug":1646},"linux-server-hardening",[503,1648,1649],{},"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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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}",{"title":102,"searchDepth":120,"depth":120,"links":1651},[1652,1653,1656,1661,1662,1663,1664,1665],{"id":34,"depth":120,"text":35},{"id":562,"depth":120,"text":563,"children":1654},[1655],{"id":624,"depth":128,"text":625},{"id":779,"depth":120,"text":780,"children":1657},[1658,1659,1660],{"id":786,"depth":128,"text":787},{"id":853,"depth":128,"text":854},{"id":917,"depth":128,"text":918},{"id":1097,"depth":120,"text":1098},{"id":1345,"depth":120,"text":1346},{"id":1465,"depth":120,"text":1466},{"id":1529,"depth":120,"text":1530},{"id":1629,"depth":120,"text":1630},"2026-05-16","A step-by-step guide to securing your Linux server — SSH hardening, firewall setup, fail2ban, automatic security updates, and more. Harden your server in under an hour.","\u002Fimages\u002Fblog\u002Flinux-server-hardening\u002Fheader.webp","A terminal showing active firewall and fail2ban protection on a Linux server","A hardened Linux server — firewall active, fail2ban watching, root login disabled.",{},"\u002Fblog\u002Flinux-server-hardening",{"title":536,"description":1667},"blog\u002Flinux-server-hardening",[528,1676,529,1677],"security","devops","XjdP8s_N-VGGnTkwWa63x7lJskx0K3uayLylPQBU93Q",1782795477503]