Step-by-Step Guide to Hosting Your Website on a VPS Using Caddy Server and Cloudflare
Hosting your frontend application on Vercel or Netlify is quite simple; you just need to provide access to your repository and the build command. Recently, I moved my personal portfolio from Next.js to Astro because my portfolio has more static content. My previous Next.js site was deployed on both Vercel and Netlify, so it was easy to deploy by just changing the build command. However, since I recently bought a Virtual Private Server (VPS) from Digital Ocean (Droplet), I decided to host my portfolio there and serve it through Cloudflare.
In this article, we'll explore how I achieved this using my VPS, Caddy server, and Cloudflare, and how you can do it too by following the steps. But first, let's briefly understand what the Caddy server and Cloudflare are.
Cloudflare:
You might already know about Cloudflare, a well-known company that offers services to enhance website performance and security. I'm using Cloudflare's reverse proxy service, which is nearly free. You might wonder, what is a reverse proxy? Simply put, a reverse proxy is a server that sits between my VPS and the user. It acts as a protective layer between my website and the public, helping to hide my server's IP address and prevent certain types of malware and cyberattacks.
Caddy server:
Caddy server is a free and open-source web server written in Golang. You might have heard of Apache or Nginx web servers, right? Caddy is similar to those, but it is secure by default. Caddy automatically gets and renews TLS certificates, making secure HTTPS connections easy.
P.S. I should mention that I already have my domain and a Cloudflare account. I also purchased a VPS from DigitalOcean with a Debian setup. This article won't cover these setups.
Adding VPS IP to Cloudflare
First, go to your Cloudflare dashboard and, in the Website section, add the domain name you want to host on your VPS. I've already added mine, and it should look something like this:
First, let's set up our SSL/TLS encryption. On the Cloudflare dashboard, click on the domain name you added (for example, dushmanta.dev in my case). This will open the domain overview page and settings, which should look like this:
Set SSL/TLS encryption mode:
On the domain settings page, go to the SSL/TLS section (refer to the image above). It should look like this:
From the SSL/TLS overview page, go to the configuration settings. It should look something like this:
In this section, set the custom SSL/TLS to "Full (Strict)" and save it.
The reason to choose "Full (Strict)" is to ensure the request connection is encrypted from end to end. This means when someone visits your domain, the request goes to Cloudflare, and then Cloudflare serves the content by connecting to your VPS. The flow looks like this:
User → Cloudflare: HTTPS
Cloudflare → Your VPS: HTTPS with a validated certificate
VPS → Cloudflare
Cloudflare → User (HTTPS)
Get Your Droplet (VPS) Ipv4
Now, let's get the IP of the VPS from the DigitalOcean dashboard. The dashboard should look like this, and you need to copy your IPv4 address.
Add “A” records to Cloudflare DNS
On the domain settings page, go to the DNS section.
Now, if you have used Cloudflare for your website before, you might have some "A", "AAAA", "CNAME", and "TXT" records. In my case, since I deployed my site with Netlify, my DNS records from Netlify looked something like this:
Before adding our VPS server's IP, we need to delete all the "A", "AAAA", and "CNAME" records (only for the root domain) if they exist. After removing them, we need to add "A" and "CNAME" DNS records pointing to the VPS IP address.
"A" record: Name → @, VPS IPv4 address, Proxy status → on
"CNAME" record: Name → WWW, Target → Original domain name, Proxy status → on
When adding a new DNS record, set the type to "A". In the Name section, add @, which represents your website (in my case, it’s dushmanta.dev), and ensure the proxy status is turned on. Then save it. It should look like this.
Next, add a "CNAME" record named "WWW" to create an alias. This way, if someone accesses the website using www.dushmanta.dev, it will redirect to the main website. Set your website as the target and ensure the proxy status is turned on. It should look like this.
Now that our Cloudflare DNS is set up, let's move on to build the portfolio from the repository and configuring Caddy on our VPS to manage the requests.
Build the Portfolio on VPS
Create a clone of your repository from GitHub, GitLab, or wherever your portfolio code is hosted onto your VPS, if you haven’t already. Next, run a build of your portfolio using the required command to generate the output in the dist folder, if you’re using a framework. If you're using vanilla JavaScript with HTML and CSS, simply place your HTML, CSS, and JS files in the dist folder. Now, the build output will be in the dist folder.
Now we can serve the build files from this repo, but for better organization, I recommend creating a separate folder for the build files. This way, if we want to host more websites on our VPS in the future, it will be easier to manage.
Create a Directory
On your VPS, create a directory for your site with the following command. (Since I’m hosting my portfolio, I will name the directory "portfolio.")
sudo mkdir -p /var/www/your-site
# sudo mkdir -p /var/www/portfolio
Copy dist Folder
Next, copy the build files from the repository to the dedicated directory using the following command:
sudo cp -r dist/* /var/www/your-site/
# sudo cp -r dist/* /var/www/portfolio/
Install and Set Up Caddy Server
Install the Caddy server on your VPS according to your distribution by following the installation docs. There are two releases: a stable release and a testing release. It is recommended to use the stable release to avoid any issues. My server is Debian, so I will install the apt
package.
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Set Proper Permission
Next, give Caddy the correct permissions for the folder where the build files are located using the following command:
sudo chown -R caddy:caddy /var/www/your-site
# sudo chown -R caddy:caddy /var/www/portfolio
Setting Up Caddy Service Configuration
Now, let's verify or create the systemd
Caddy service configuration. This setup ensures that the systemd service file automatically manages Caddy's lifecycle, which includes automatic starts, restarts, and proper resource management on the server.
Open the service file with the following command (this command will create the file if it doesn't exist):
sudo nano /etc/systemd/system/caddy.service
The service file should look something like this:
[Unit]
Description=Caddy web server
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target
[Service]
Type=notify
ExecStart=/usr/bin/caddy run --config /etc/caddy/Caddyfile
TimeoutStopSec=5s
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
If it's empty, update the file with the contents provided above and be sure to save it.
Notes:
This configuration makes sure Caddy starts automatically with your system.
It waits for network connectivity before starting.
The service is set up to handle high loads with
LimitNOFILE
.
After saving the configuration, make sure to follow the next steps:
Reload systemd daemon:
sudo systemctl daemon-reload
Start Caddy service:
sudo systemctl start caddy
Check status:
sudo systemctl status caddy
Enable auto-start:
sudo systemctl enable caddy
Configure the Caddyfile
After setting the correct permissions and configuring the systemd
Caddy service file, let's set up the Caddyfile. This is where we configure our domain settings and file serving options. To learn more about the Caddyfile, check out the official documentation.
To edit the Caddyfile, run the following command:
sudo nano /etc/caddy/Caddyfile
Basic Configuration Structure:
Domain names (both root and www)
TLS configuration for Cloudflare SSL
Root directory path (your build folder)
File server directive (helps serve web pages from the directory mentioned above)
Here's how the Caddyfile should look:
example.com, www.example.com {
# TLS configuration for Cloudflare
tls {
protocols tls1.2 tls1.3
}
root * /var/www/site-name # the build folder
file_server # File server directive
}
Example Implementation:
For my website, the Caddyfile looks like this:
dushmanta.dev, www.dushmanta.dev {
tls {
protocols tls1.2 tls1.3
}
root * /var/www/portfolio
file_server
}
Save the configuration file and then restart the Caddy service with the following command:
sudo systemctl restart caddy
And voilà! You've set up your Caddy server on your VPS, and it's ready to be served through Cloudflare.
Testing Your Configuration
Now, test if it works by using curl for both DNS records.
curl -v https://example.com
curl -v https://www.example.com
# Example
# curl -v https://dushmanta.dev
# curl -v https://www.dushmanta.dev
If you find any errors, make sure to configure your Caddy service and Caddyfile correctly, and check that your DNS settings are correct in your Cloudflare dashboard.
I hope this article helps if you're trying to host your website on your personal VPS with Caddy server and Cloudflare. Please let me know if you find any mistakes or have suggestions for improvement. Thank you, and happy coding! :)
Subscribe to my newsletter
Read articles from Dushmanta directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Dushmanta
Dushmanta
Passionate Software Engineer