Hills 🏔 and Skills, What's Common?

They both need you to be on top.

Cohort-1 just ended. You will get:

All yours, just at:

$99

Is Caddy the new daddy of Nginx? — Setup Caddy on EC2 to find out

Updated on
guy drawning because he couldnt figure out nginx

Hello Caddy, Goodbye Nginx.

Why setting up SSL is so hard? Can’t SSL be automatically setup for our servers?

I just can’t understand and remember all the Nginx config syntax and file locations. I need an easy way out, please … Save me! Help me!

Worry not …

Caddy is here to save your ass 🍑

Let’s start by installing Caddy in our server

Install Caddy on Amazon Linux using yum

This will work with Fedora, RedHat and CentOS without the epel-7-$(arch) argument.

If you’re not logged into your root account, then you’ll need to use sudo

yum -y install yum-plugin-copr
yum -y copr enable @caddy/caddy epel-7-$(arch)
yum -y install caddy

To install Caddy on Debian, Ubuntu, Raspbian:

Use your apt package manager to import the keys and install Caddy

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
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

Universal Linux / MacOS installer

If nothing works out you can install it using the webi.sh installer on any Linux or Mac operating system with curl:

curl -sS https://webi.sh/caddy | sh

Installation complete

Here is what you’ll see at the time of installation:

$ sudo yum -y install caddy
Copr repo for caddy owned by @caddy                                                                                                         1.0 kB/s | 2.5 kB     00:02
Last metadata expiration check: 0:00:01 ago on Fri Aug 11 19:44:42 2023.
Dependencies resolved.
============================================================================================================================================================================
 Package                     Architecture                 Version                              Repository                                                              Size
============================================================================================================================================================================
Installing:
 caddy                       x86_64                       2.7.3-1.el7                          copr:copr.fedorainfracloud.org:group_caddy:caddy                        24 M

Transaction Summary
============================================================================================================================================================================
Install  1 Package

Total download size: 24 M
Installed size: 60 M
Downloading Packages:
caddy-2.7.3-1.el7.x86_64.rpm                                                                                                                7.5 MB/s |  24 MB     00:03
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Total                                                                                                                                       7.5 MB/s |  24 MB     00:03
Copr repo for caddy owned by @caddy                                                                                                          14 kB/s | 994  B     00:00
Importing GPG key 0xD605147E:
 Userid     : "@caddy_caddy (None) <@caddy#caddy@copr.fedorahosted.org>"
 Fingerprint: 4A76 F92F F6D4 0440 F8FC 4F36 C521 91B1 D605 147E
 From       : https://download.copr.fedorainfracloud.org/results/@caddy/caddy/pubkey.gpg
Key imported successfully
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                                                                                                                    1/1
  Running scriptlet: caddy-2.7.3-1.el7.x86_64                                                                                                                           1/1
  Installing       : caddy-2.7.3-1.el7.x86_64                                                                                                                           1/1
  Running scriptlet: caddy-2.7.3-1.el7.x86_64                                                                                                                           1/1
  Verifying        : caddy-2.7.3-1.el7.x86_64                                                                                                                           1/1
============================================================================================================================================================================
Installed:
  caddy-2.7.3-1.el7.x86_64

Complete!

Testing the Caddy server

Simply run caddy run it’ll start it’s admin API server.

 caddy run                                                 
2023/08/11 20:06:49.972	INFO	admin	admin endpoint started	{"address": "localhost:2019", "enforce_origin": false, "origins": ["//127.0.0.1:2019", "//localhost:2019", "//[::1]:2019"]}
2023/08/11 20:06:49.972	INFO	serving initial configuration

Calling Admin APIs

If you simply do curl localhost:2019, you’ll get a 404 Page Not Found.

 curl localhost:2019        
404 page not found

That’s unsettling! But it is expected.

You can learn more about Caddy admin API here.

But just to show you a demo, we can stop the server by making a POST request to the /stop endpoint.

curl -X POST "http://localhost:2019/stop"

You’ll see Caddy saying “byeee!! 👋” on the terminal where you ran Caddy:

2023/08/11 20:18:39.553	INFO	admin.api	received request	{"method": "POST", "host": "localhost:2019", "uri": "/stop", "remote_ip": "127.0.0.1", "remote_port": "56018", "headers": {"Accept":["*/*"],"User-Agent":["curl/8.2.1"]}}
2023/08/11 20:18:39.553	WARN	admin.api	exiting; byeee!! 👋
2023/08/11 20:18:39.553	INFO	admin	stopped previous server	{"address": "localhost:2019"}
2023/08/11 20:18:39.553	INFO	admin.api	shutdown complete	{"exit_code": 0}

And the server will be gracefully shut down.

Use Caddy files to serve requests

Simply create a text file in /etc/caddy called Caddyfile (no extensions)

sudo vim Caddyfile

And write the config below which will simply respond with a text:

localhost

respond "Hello, Daddy!"

Now save and exit from your editor :wq.

You can see all the available options of Caddyfile here.

Start Caddy server with config

Run caddy start to start your Caddy server.

Soon enough you’ll be greeted with a nice error saying listen tcp :443: bind: permission denied.

2023/08/11 20:25:31.047	INFO	using adjacent Caddyfile
2023/08/11 20:25:31.049	INFO	admin	admin endpoint started	{"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2023/08/11 20:25:31.049	INFO	http	server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS	{"server_name": "srv0", "https_port": 443}
2023/08/11 20:25:31.049	INFO	http	enabling automatic HTTP->HTTPS redirects	{"server_name": "srv0"}
2023/08/11 20:25:31.049	INFO	tls.cache.maintenance	started background certificate maintenance	{"cache": "0xc000460cb0"}
2023/08/11 20:25:31.050	INFO	tls.cache.maintenance	stopped background certificate maintenance	{"cache": "0xc000460cb0"}
Error: loading initial config: loading new config: http app module: start: listening on :443: listen tcp :443: bind: permission denied
Error: caddy process exited with error: exit status 1

This error is telling us that Caddy doesn’t have the permission to run on port 80 and 443.

Fix TCP permission denied error

We allow Caddy to run on ports below 1000 like 80 and 443 using setcap

sudo setcap cap_net_bind_service=+ep $(which caddy)

If there is no error, you’re good to go.

If you get an error saying: Failed to set capabilities on file node (No such file or directory)

You may get this error if the Caddy path is symbolic link AKA symlink.

To fix this simply find out where Caddy was installed run which caddy, it’ll give you the full path of the binary.

To check if there is symlink, run ls -la

[ec2-user@ip-172-31-12-247 ~]$ ls -la .local/bin/
total 8
drwxr-xr-x. 2 ec2-user ec2-user   31 Aug 11 19:12 .
drwxrwxr-x. 5 ec2-user ec2-user   41 Aug 11 19:12 ..
lrwxrwxrwx. 1 ec2-user ec2-user   48 Aug 11 19:12 caddy -> /home/ec2-user/.local/opt/caddy-v2.7.3/bin/caddy
-rwxr-xr-x. 1 ec2-user ec2-user 5274 Aug 11 19:12 webi

Now you can put the actual path of Caddy instead of $(which caddy).

Stop and Uninstall Nginx

Sometimes it can be hard to say goodbyes, but we do it anyways. Don’t we? Let’s say goodbye to our beloved Nginx.

sudo systemctl stop nginx

Remove it from your system using the package manager:

sudo yum remove nginx

Feel free to use sudo apt remove nginx on Debian based distros like Ubuntu.

Press Y and hit Enter!

Dependencies resolved.
============================================================================================================================================================================
 Package                                      Architecture                    Version                                           Repository                             Size
============================================================================================================================================================================
Removing:
 nginx                                        x86_64                          1:1.22.1-1.amzn2023.0.3                           @amazonlinux                          152 k
Removing unused dependencies:
 generic-logos-httpd                          noarch                          18.0.0-12.amzn2023.0.3                            @amazonlinux                           21 k
 gperftools-libs                              x86_64                          2.9.1-1.amzn2023.0.2                              @amazonlinux                          1.4 M
 libunwind                                    x86_64                          1.4.0-5.amzn2023.0.2                              @amazonlinux                          165 k
 nginx-core                                   x86_64                          1:1.22.1-1.amzn2023.0.3                           @amazonlinux                          1.6 M
 nginx-filesystem                             noarch                          1:1.22.1-1.amzn2023.0.3                           @amazonlinux                            0
 nginx-mimetypes                              noarch                          2.1.49-3.amzn2023.0.3                             @amazonlinux                           43 k

Transaction Summary
============================================================================================================================================================================
Remove  7 Packages

Freed space: 3.4 M
Is this ok [y/N]: y
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                                                                                                                    1/1
  Running scriptlet: nginx-1:1.22.1-1.amzn2023.0.3.x86_64                                                                                                               1/7
Removed "/etc/systemd/system/multi-user.target.wants/nginx.service".

  Erasing          : nginx-1:1.22.1-1.amzn2023.0.3.x86_64                                                                                                               1/7
  Running scriptlet: nginx-1:1.22.1-1.amzn2023.0.3.x86_64                                                                                                               1/7
  Erasing          : generic-logos-httpd-18.0.0-12.amzn2023.0.3.noarch                                                                                                  2/7
  Erasing          : nginx-core-1:1.22.1-1.amzn2023.0.3.x86_64                                                                                                          3/7
  Erasing          : nginx-mimetypes-2.1.49-3.amzn2023.0.3.noarch                                                                                                       4/7
  Erasing          : gperftools-libs-2.9.1-1.amzn2023.0.2.x86_64                                                                                                        5/7
  Erasing          : nginx-filesystem-1:1.22.1-1.amzn2023.0.3.noarch                                                                                                    6/7
  Erasing          : libunwind-1.4.0-5.amzn2023.0.2.x86_64                                                                                                              7/7
  Running scriptlet: libunwind-1.4.0-5.amzn2023.0.2.x86_64                                                                                                              7/7
  Verifying        : generic-logos-httpd-18.0.0-12.amzn2023.0.3.noarch                                                                                                  1/7
  Verifying        : gperftools-libs-2.9.1-1.amzn2023.0.2.x86_64                                                                                                        2/7
  Verifying        : libunwind-1.4.0-5.amzn2023.0.2.x86_64                                                                                                              3/7
  Verifying        : nginx-1:1.22.1-1.amzn2023.0.3.x86_64                                                                                                               4/7
  Verifying        : nginx-core-1:1.22.1-1.amzn2023.0.3.x86_64                                                                                                          5/7
  Verifying        : nginx-filesystem-1:1.22.1-1.amzn2023.0.3.noarch                                                                                                    6/7
  Verifying        : nginx-mimetypes-2.1.49-3.amzn2023.0.3.noarch                                                                                                       7/7
============================================================================================================================================================================

Run caddy start

Once you give Caddy the port permissions and start it again, you’ll get to see:

Successfully started Caddy (pid=138022) - Caddy is running in the background

If you run curl localhost you’ll get nothing.

Wondering why?

Let’s run curl again, with -i flag:

 curl localhost -i
HTTP/1.1 308 Permanent Redirect
Connection: close
Location: https://localhost/
Server: Caddy
Date: Fri, 11 Aug 2023 20:44:26 GMT
Content-Length: 0

We get to see that there is permanent redirect with status 308.

By default Caddy will redirect the requests to HTTPS version

If you want to follow the redirect just add --location flag at the end of the curl command.

 curl localhost --location
Hello, Daddy!

Finally, after all the hard work. We see Daddy! ~ from Caddy

daddy cool rasputin

Listen to Daddy Cool!

Install SSL certificate for your custom domain

Localhost is not something you can continue your life with, you and your user needs custom domain with SSL.

Make sure you have your domain pointed to your server IP.

Replace localhost in the Caddyfile with your custom-domain.com you can put your subdomains as well, like www or app.

Reload Caddy using caddy reload command.

You’ll see a message saying “certificate obtained successfully”.

2023/08/11 19:32:01.414 INFO    using adjacent Caddyfile
2023/08/11 19:32:01.415 WARN    Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies    {"adapter": "caddyfile", "file": "Caddyfile", "line": 4}
2023/08/11 19:32:01.416 INFO    admin.api       received request        {"method": "POST", "host": "localhost:2019", "uri": "/load", "remote_ip": "127.0.0.1", "remote_port": "49072", "headers": {"Accept-Encoding":["gzip"],"Content-Length":["240"],"Content-Type":["application/json"],"Origin":["http://localhost:2019"],"User-Agent":["Go-http-client/1.1"]}}
2023/08/11 19:32:01.416 INFO    admin   admin endpoint started  {"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2023/08/11 19:32:01.416 INFO    http.auto_https server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS {"server_name": "srv0", "https_port": 443}
2023/08/11 19:32:01.416 INFO    http.auto_https enabling automatic HTTP->HTTPS redirects        {"server_name": "srv0"}
2023/08/11 19:32:01.417 INFO    http    enabling HTTP/3 listener        {"addr": ":443"}
2023/08/11 19:32:01.417 INFO    http.log        server running  {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2023/08/11 19:32:01.417 INFO    http.log        server running  {"name": "remaining_auto_https_redirects", "protocols": ["h1", "h2", "h3"]}
2023/08/11 19:32:01.417 INFO    http    enabling automatic TLS certificate management   {"domains": ["store.curiosta.com"]}
2023/08/11 19:32:01.417 INFO    http    servers shutting down with eternal grace period
2023/08/11 19:32:01.418 INFO    tls.obtain      acquiring lock  {"identifier": "store.curiosta.com"}
2023/08/11 19:32:01.418 INFO    autosaved config (load with --resume flag)      {"file": "/home/ec2-user/.config/caddy/autosave.json"}
2023/08/11 19:32:01.418 INFO    admin.api       load complete
[ec2-user@ip-172-31-12-247 ~]$ 2023/08/11 19:32:01.421  INFO    tls.obtain      lock acquired   {"identifier": "store.curiosta.com"}
2023/08/11 19:32:01.421 INFO    tls.obtain      obtaining certificate   {"identifier": "store.curiosta.com"}
2023/08/11 19:32:01.422 INFO    admin   stopped previous server {"address": "localhost:2019"}
2023/08/11 19:32:02.714 INFO    http    waiting on internal rate limiter        {"identifiers": ["store.curiosta.com"], "ca": "https://acme-v02.api.letsencrypt.org/directory", "account": ""}
2023/08/11 19:32:02.715 INFO    http    done waiting on internal rate limiter   {"identifiers": ["store.curiosta.com"], "ca": "https://acme-v02.api.letsencrypt.org/directory", "account": ""}
2023/08/11 19:32:03.264 INFO    http.acme_client        trying to solve challenge       {"identifier": "store.curiosta.com", "challenge_type": "http-01", "ca": "https://acme-v02.api.letsencrypt.org/directory"}
2023/08/11 19:32:03.850 INFO    http    served key authentication       {"identifier": "store.curiosta.com", "challenge": "http-01", "remote": "18.118.13.40:61778", "distributed": false}
2023/08/11 19:32:03.952 INFO    http    served key authentication       {"identifier": "store.curiosta.com", "challenge": "http-01", "remote": "35.90.59.29:43326", "distributed": false}
2023/08/11 19:32:03.970 INFO    http    served key authentication       {"identifier": "store.curiosta.com", "challenge": "http-01", "remote": "23.178.112.107:64588", "distributed": false}
2023/08/11 19:32:04.650 INFO    http.acme_client        authorization finalized {"identifier": "store.curiosta.com", "authz_status": "valid"}
2023/08/11 19:32:04.651 INFO    http.acme_client        validations succeeded; finalizing order {"order": "https://acme-v02.api.letsencrypt.org/acme/order/1253415916/200955216196"}
2023/08/11 19:32:05.952 INFO    http.acme_client        successfully downloaded available certificate chains    {"count": 2, "first_url": "https://acme-v02.api.letsencrypt.org/acme/cert/03db8f52dbe2724b7686405505656f1f0bde"}
2023/08/11 19:32:05.952 INFO    tls.obtain      certificate obtained successfully       {"identifier": "store.curiosta.com"}
2023/08/11 19:32:05.952 INFO    tls.obtain      releasing lock  {"identifier": "store.curiosta.com"}

Visit your website from your browser or curl it to see if you’re seeing daddy or not.

If you have followed all the steps carefully you should see the Hello Daddy.

You don’t need to manually renew or do anything for SSL certificate.

Enable Caddy service to keep it running in background

You don’t want your server to die the moment you close your terminal, do you? No you don’t.

If you have installed Caddy using your system package manager, you should have caddy.service installed in your system.

All you need to do it enable it:

sudo systemctl enable --now caddy
# to enable api service
sudo systemctl enable --now caddy-api

With this the Caddy service will be enabled and started immediately and automatically start on boot as well.

If you don’t have the service already, you can manually create by following this guide.

Now your Caddy server will keep running even when you sleep unless your server takes a nap too.

Use Caddy as reverse proxy

Most of us don’t just host static sites we love to use NodeJS, Python and Java to run our servers behind a proxy.

Caddy can definitely do that too. Edit your Caddyfile with reverse_proxy param and pass your application port.

example.com

reverse_proxy :9000 # Whatever your port is

Use Caddy for serving static files

S3 is a better choice but hey, we love EC2 and our VPS more than anything. Okay I won’t lecture you anymore, take this:

example.com

root * /home/me/mysite
file_server

Multiple paths for API and File server

We usually have a /api for doing API calls and we serve static content like HTML on the root, you can do this by following the example below:

localhost

encode zstd gzip # enable compression
root * /home/me/mysite
file_server
reverse_proxy /api/* 127.0.0.1:9005

That’s all about Caddy?

No! There is more. Always has been.

more to caddy meme

You can learn more about Caddy from their website.

They have way more features like dynamic HTML templating, multiple server blocks, CLI guide and much more. I just couldn’t cover everything in this blog, but this should give you a kick-start or just enough guidance to keep your server running with HTTP/3 and free SSL.

I wish you all the best with your new Daddy Caddy ;)

Don’t forget to leave your thoughts in the comments.