In the previous post, I walked through setting up a bare-ish-metal cloud environment with Linode, partitioning public and private subnets, and wiring up your own proxies, and firewalls — without handing everything off to someone else. If you haven't read it, I suggest you do!
Today, I intend to go one level deeper:
For basic learning purposes, let’s build a secure SFTP server from scratch using the node in our public subnet.
Why should you do this you ask?
Because file transfer is a foundational primitive in ops, and there’s no reason to let that knowledge slip through the cracks.
Why Build Your Own SFTP Server?
- You don’t need to rely on a SaaS or managed service (lots of manual ops btw)
- You control access, retention, and isolation
- You understand how file access and security actually work under the hood
- You can build automations and workflows around it
This is especially useful when:
- You’re collaborating with partners who need to send you files securely
- You want to ship logs, reports, or ETL inputs into your infra
- You’re learning how SSH, chroot jails, and Linux permissions actually work
Prerequisites
- A provisioned Linode instance in your public subnet
- A public IP address and port
22
open to trusted IPs - Basic Linux CLI knowledge
I handled the first 2 points in this article
We'll be using Ubuntu 22.04 LTS, but this works on most distros with openssh-server
.
Step-by-Step Setup
1. Install OpenSSH
Make sure SSH is installed and running:
sudo apt update && sudo apt install openssh-server -y
2. Create an SFTP-Only User
sudo groupadd sftpusers
sudo useradd -m -G sftpusers -s /sbin/nologin sftpuser1
sudo passwd sftpuser1
This prevents shell access and groups users logically.
3. Create a Secure Directory Structure
OpenSSH’s ChrootDirectory
requires that the parent dir is owned by root and not writable.
sudo mkdir -p /sftp/sftpuser1/upload
sudo chown root:root /sftp/sftpuser1
sudo chmod 755 /sftp/sftpuser1
sudo chown sftpuser1:sftpusers /sftp/sftpuser1/upload
This creates a writable /upload
directory while keeping the jail secure.
4. Configure sshd_config
for SFTP Jail
Append this block to the bottom of /etc/ssh/sshd_config
(sudo nano /etc/ssh/sshd_config
to open):
Match Group sftpusers
ChrootDirectory /sftp/%u
ForceCommand internal-sftp
X11Forwarding no
AllowTcpForwarding no
Then reload SSH:
sudo systemctl restart ssh
5. Use SSH Key Authentication
On your local machine:
ssh-keygen -t rsa -b 4096 -f ~/.ssh/sftpuser1_key
On your server:
sudo mkdir -p /home/sftpuser1/.ssh
sudo nano /home/sftpuser1/.ssh/authorized_keys
# Paste public key here
sudo chown -R sftpuser1:sftpusers /home/sftpuser1/.ssh
sudo chmod 700 /home/sftpuser1/.ssh
sudo chmod 600 /home/sftpuser1/.ssh/authorized_keys
You can now disable password login if you wish.
6. Secure the Server
- Open port 22 to only your office or VPN IP
- Install fail2ban:
sudo apt install fail2ban
- Consider using logrotate and basic audit logging
7. Test the Setup
From your local terminal:
sftp -i ~/.ssh/sftpuser1_key sftpuser1@<your-linode-ip>
Then:
cd /upload
put testfile.txt
Tip: SFTP isn’t a shell. You can’t run
cat
orecho
— justput
,get
,ls
, etc.
Why Public Subnet?
Because this server needs to be accessed from the internet. If it were in a private subnet, you’d need a bastion or VPN to reach it — useful for internal automation, but not external sharing.
Just like with your previous setup:
- The public subnet gives controlled external access
- Security is enforced via firewall + SSH key access
Lessons Reinforced
- Chroot directories must be owned by root
- SFTP can be a secure alternative to email attachments or third-party tools
- You can still own your file flows in a modern, cloud-native way
Next Steps
For future improvements and personal learning growth:
- Automate uploads from other services or cron jobs
- Pipe incoming files into a processing queue (e.g., via inotify or systemd)
- Back up uploaded files to S3
- Add a DNS record if you want:
sftp.yourdomain.com
→ your Linode IP
Final Thoughts
Owning your infra doesn't mean reinventing everything — it means understanding the tradeoffs and being able to build what you need, when you need it. This is one more building block toward that confidence.
You’ve got this. Nothing is impossible
Top comments (0)