A few years ago, on the VPS where I hosted my private financial calculators, I woke up one morning to see hundreds of failed SSH login attempts in the logs. fail2ban had kicked in, blocking many of them, but this incident once again focused my attention on the security responsibility that comes with self-hosting. While the promise of full control is appealing, underestimating the security burden that comes with this control can lead to serious problems.
In my 20 years of experience managing my own systems, I’ve seen that self-hosting is both a freedom and a heavy responsibility. Even if our initial intention is just to “get something running,” over time, considering security at every layer of the system becomes a necessity. In this post, I’ll explain what I pay attention to regarding security when self-hosting and what practices I apply.
Why Do We Choose Self-Hosting and What Are Its Risks?
We usually opt for self-hosting for more control, cost savings, or simply out of curiosity to learn and experiment. The feeling of commanding every millimeter of your own hardware or virtual server is attractive to many developers and system administrators. When developing my side projects or when a client project has specific needs, flexibility is often a critical factor, and it can be difficult to find this flexibility in the cloud at the same cost.
However, this control also brings significant risks. Many security layers that a cloud provider manages for you in the background (such as physical security, network isolation, operating system patches) fall entirely on your shoulders with self-hosting. A misconfigured firewall, an outdated operating system, or a weak password policy can leave your system vulnerable to cyberattacks. In my experience, because the initial focus is usually just on getting the application to work, these fundamental security steps are either skipped or postponed, leading to a large accumulation of technical debt over time.
Basic System Security: Operating System and Services
The operating system, the core of your server, forms the foundation of your security strategy. Even the smallest vulnerability here can lead to the entire system being compromised. Therefore, operating system-level security settings should never be overlooked.
First, regularly tracking and applying operating system patches is a must. CVE tracking is very important in this regard. For example, when a vulnerability (CVE-2026-31431) in a critical kernel module, algif_aead, recently emerged, I immediately blacklisted the module to close a potential attack surface. While I generally keep automatic updates active on my systems with tools like unattended-upgrades, I prefer to manually check and quickly apply critical patches.
# Example: blacklisting a kernel module
echo "blacklist algif_aead" | sudo tee /etc/modprobe.d/algif_aead_blacklist.conf
sudo update-initramfs -u
Securing SSH access is also a critical step. Using only SSH keys instead of password-based logins and preventing brute-force attacks with tools like fail2ban is a fundamental requirement. On my own servers, I write custom filters for fail2ban to more effectively block specific attack patterns I frequently encounter. Additionally, monitoring important file accesses and system calls with the auditd subsystem is a good way to catch unexpected activities. Using mandatory access control mechanisms like SELinux or AppArmor with correct profiles also limits the spread of an attack if a service or application is compromised.
Network Security: Defining and Monitoring Boundaries
The network layer is the first line of defense between your server and the outside world. A misconfigured network can render all other security measures meaningless. While developing the backend for a production ERP, I’ve seen countless times how dangerous it is for application servers to be directly exposed to the internet.
graph TD;
A["Client (Browser/App)"] --> B["Internet"];
B --> C["Firewall"];
C --> D["Nginx (Reverse Proxy / Load Balancer)"];
D --> E["Application Server (FastAPI/Node.js)"];
E --> F["Database (PostgreSQL/Redis)"];
E --> G["Other Services (MQ/Cache)"];
subgraph "Security Layers"
C -- "Packet Filtering" --> D;
D -- "Rate Limiting / WAF" --> E;
E -- "Authentication / Authorization" --> F;
E -- "Data Validation" --> G;
end
Network segmentation is vital, especially if you’re hosting multiple services or applications. Isolating different services or environments with VLAN tagging makes it harder for an attack to spread to others if one segment is compromised. For example, I place database servers on a different VLAN from application servers and define firewall rules that only allow communication between them over necessary ports.
Using a reverse proxy like Nginx is a great way to filter and balance incoming traffic. I use Nginx not only for SSL termination and load balancing but also for rate limiting and basic WAF (Web Application Firewall) features. This way, I block malicious requests before they even reach my application servers.
# Simple rate limiting example with Nginx
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=5r/s;
server {
listen 80;
server_name example.com;
location / {
limit_req zone=mylimit burst=10 nodelay;
proxy_pass http://backend_servers;
# Other proxy settings
}
}
Additionally, remote access needs to be secured with VPN or Zero Trust Network Access (ZTNA) solutions. For accessing a company’s internal networks, I used the ZTNA model to allow only specific users, from specific devices, to access specific applications. This provides much more granular security than a traditional VPN. Even seemingly small network details like MTU/MSS mismatches should be carefully examined, as they can lead to DoS attacks on tunneled traffic in some cases. By enabling features like DHCP snooping, Dynamic ARP Inspection (DAI), and IP Source Guard on switches, we can also prevent some in-network attacks.
Application Security: Development and Deployment Processes
After laying the foundations of system and network security, it’s time for the security of the applications you develop or use. Vulnerabilities at the application layer are often the easiest to exploit and lead to the most devastating consequences.
When designing operator screens for a production ERP, I operated under the assumption that every piece of data from the user is a potential threat. Proactive measures against common attack types like SQL injection and Cross-Site Scripting (XSS) are essential. Sanitizing all incoming inputs and using appropriate encoding in outputs is a basic rule. While using an ORM provides some protection in this regard, care must still be taken with custom queries.
Authentication and authorization mechanisms are also very important. When using modern patterns like JWT (JSON Web Token) or OAuth2, it’s crucial to ensure that tokens are correctly signed, have short validity periods, and are stored securely. In the backend of my side project, I apply rate limiting to every API endpoint, both preventing brute-force attacks and protecting my service from overload.
Integrating security scans (static code analysis, dependency analysis) into CI/CD pipelines is a good way to catch vulnerabilities during the development phase. Even when using deployment strategies like dark launch or feature flags, the security implications of new features should not be overlooked.
Database Security and Data Integrity
The database stores the most valuable asset of your applications: data, so its security must be kept at the highest level. I once saw a risk of unauthorized access due to a misconfigured connection pool in a PostgreSQL database for a client project.
Specifically for PostgreSQL, user authorizations need to be managed very carefully. It’s important to create a separate database user for each application or service and grant these users access only to the tables and operations they need. When configuring connection pool settings, the maximum number of connections and connection timeout durations should be set with a balance of security and performance in mind. Excessive connection requests can be considered a DoS (Denial of Service) attack.
If you’re using an in-memory database or cache like Redis, you must ensure that the data there is also secure. Adding password protection with Redis’s requirepass feature and allowing access only from trusted IP addresses are fundamental steps. Thanks to network segmentation, allowing only application servers to access the Redis server prevents direct attacks from the outside.
# Example password setting in Redis configuration file (redis.conf)
requirepass your_strong_password
bind 127.0.0.1 192.168.1.100 # Allow access only from specific IPs
To ensure data integrity, using file integrity monitoring (FIM) tools to track unexpected changes in critical database files or configuration files is beneficial. Additionally, performing regular backups and storing these backups in a secure, isolated environment increases your chances of recovery in case of data loss or corruption. In PostgreSQL, I create read replicas using methods like logical replication or physical replication to both distribute the load and ensure data continuity against a potential disaster.
Monitoring, Backup, and Incident Management
Security doesn’t end with taking precautions; continuous monitoring, regular backups, and incident planning are also critical components. No matter how secure your system is, there’s always a chance something could go wrong.
Applying observability principles allows me to detect security vulnerabilities or attack attempts in my systems at an early stage. Collecting metrics (CPU, memory, disk I/O, network traffic), logs (application logs, system logs, firewall logs), and traces (the flow of requests within the system) is indispensable for understanding the root cause of an incident. By setting up anomaly-based monitoring systems, I ensure they automatically detect deviations from normal traffic patterns and alert me. For example, an abnormal number of failed login attempts from a specific IP address or a high database query load could be a sign of a potential attack.
Incident management is a roadmap that defines what to do in case of a security breach. This plan should include steps for detecting, isolating, eradicating, recovering, and applying lessons learned to prevent similar future incidents. On my own systems, I have a simple checklist that includes which service to stop first, which logs to check, and how to restore from backups in case of a breach. CVE tracking and patch management are also part of this process; quickly evaluating new vulnerabilities and patching my systems ensures a continuous security posture.
Conclusion
Self-hosting offers the allure of full control but brings with it full security responsibility. This responsibility means much more than just setting up a firewall or choosing a password; it requires continuous vigilance and proactive measures at every layer, from the operating system kernel to application code, network configuration to database settings. In my experience, every mistake made in this process has allowed me to build a more robust structure in the next step. Remember, security is not a one-time task but an ongoing journey. Ensuring the security of your own systems is critical not only for you but also for all other elements with which your systems interact.