Problem Configuring Caddy to Restrict Access to Specific IP

1. The problem I’m having:

I can’t limit access to my route using Caddy to a single IP.
My goal is to only allow my frontend to make requests to my backend.
However, I’m having trouble finding the ideal configuration for Caddy that works.
Currently, anyone can make requests to my API.

back.example.com/download-file

2. Error messages and/or full log output:

ERR_HTTP2_PROTOCOL_ERROR

3. Caddy version:

v2.7.5 h1:HoysvZkLcN2xJExEepaFHK92Qgs7xAiCFydN5x5Hs6Q=root

4. How I installed and ran 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
sudo service caddy start

a. System environment:

ubuntu 22 from digital ocean

b. Command:

sudo service caddy start

d. My complete Caddy config:

back.example.com.br {
  @allowed remote_ip 10.10.10.10
        handle @allowed {
                reverse_proxy localhost:3001
    file_server
        }

        handle {
                abort
        }
}

example.com.br {
 reverse_proxy localhost:3000
 file_server
}

5. Links to relevant resources:

Enable the debug global option and log directive to get verbose logging.

Check the remote_ip on the logs. Is it what you expect? Or are you seeing something else?

Please read Keep Caddy Running — Caddy Documentation

I’ve learned more about Caddy configurations and ended up with the following file.

{
    debug
    log {
        output file /var/log/caddy/access.log {
            format json
        }
    }

}

example.com.br {
    reverse_proxy localhost:3000
    file_server

    route /api* {
        request_body {
            max_size 1MB
        }
        # api acess only from server ip and domain
        @serverIpOrDomain {
            remote_ip 24.199.107.244
            header Host example.com.br
        }

        handle @serverIpOrDomain {
            # only allow requests that match the specified criteria
            reverse_proxy localhost:3001
        }

        handle {
            # deny another requests
            respond "Forbidden" 403
        }
    }
}


The current behavior is that if I make a request example.com.br/api/download using Postman, I receive a 403 error, which is the expected behavior.

However, if I access the frontend example.com.br and try to interact with the page where the same request is made directly through the domain, I also receive a 403 error and can’t complete the request.

this is my log file the “remote_ip”:“179.248.199.129” is the ip from my personal pc.
its the log when i try to use /api/download in my domain page

{"level":"debug","ts":1700250990.545935,"logger":"events","msg":"event","name":"tls_get_certificate","id":"0e2450f8-4668-4d62-87d2-0a9acd45e719","origin":"tls","data":{"client_hello":{"CipherSuites":[60138,4865,4866,4867,49195,49199,49196,49200,52393,52392,49171,49172,156,157,47,53],"ServerName":"example.com.br","SupportedCurves":[6682,25497,29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[60138,772,771],"Conn":{}}}}
{"level":"debug","ts":1700250990.5464137,"logger":"tls.handshake","msg":"choosing certificate","identifier":"example.com.br","num_choices":1}
{"level":"debug","ts":1700250990.5464392,"logger":"tls.handshake","msg":"default certificate selection results","identifier":"example.com.br","subjects":["example.com.br"],"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"ea19fd89505c068d122106178232f6eb15ef2bf84e7c14e05fd7b36704e884ba"}
{"level":"debug","ts":1700250990.5464673,"logger":"tls.handshake","msg":"matched certificate in cache","remote_ip":"179.248.199.129","remote_port":"42868","subjects":["example.com.br"],"managed":true,"expiration":1707696007,"hash":"ea19fd89505c068d122106178232f6eb15ef2bf84e7c14e05fd7b36704e884ba"}
{"level":"debug","ts":1700250990.7790604,"logger":"events","msg":"event","name":"tls_get_certificate","id":"a9682b99-6e3a-46f1-a318-dc4753d6dcd0","origin":"tls","data":{"client_hello":{"CipherSuites":[39578,4865,4866,4867,49195,49199,49196,49200,52393,52392,49171,49172,156,157,47,53],"ServerName":"example.com.br","SupportedCurves":[64250,25497,29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[2570,772,771],"Conn":{}}}}
{"level":"debug","ts":1700250990.7793405,"logger":"tls.handshake","msg":"choosing certificate","identifier":"example.com.br","num_choices":1}
{"level":"debug","ts":1700250990.779387,"logger":"tls.handshake","msg":"default certificate selection results","identifier":"example.com.br","subjects":["example.com.br"],"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"ea19fd89505c068d122106178232f6eb15ef2bf84e7c14e05fd7b36704e884ba"}
{"level":"debug","ts":1700250990.779401,"logger":"tls.handshake","msg":"matched certificate in cache","remote_ip":"179.248.199.129","remote_port":"42878","subjects":["example.com.br"],"managed":true,"expiration":1707696007,"hash":"ea19fd89505c068d122106178232f6eb15ef2bf84e7c14e05fd7b36704e884ba"}
{"level":"debug","ts":1700251049.7826915,"logger":"events","msg":"event","name":"tls_get_certificate","id":"485a8e14-fc92-43c0-93f9-ca9db9916edd","origin":"tls","data":{"client_hello":{"CipherSuites":[4865,4866,4867,49199,49195,49200,49196,52393,52392,49161,49171,49162,49172,156,157,47,53,10],"ServerName":"example.com.br","SupportedCurves":[29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537,513],"SupportedProtos":null,"SupportedVersions":[772,771,770,769],"Conn":{}}}}
{"level":"debug","ts":1700251049.7828076,"logger":"tls.handshake","msg":"choosing certificate","identifier":"example.com.br","num_choices":1}
{"level":"debug","ts":1700251049.7828395,"logger":"tls.handshake","msg":"default certificate selection results","identifier":"example.com.br","subjects":["example.com.br"],"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"ea19fd89505c068d122106178232f6eb15ef2bf84e7c14e05fd7b36704e884ba"}
{"level":"debug","ts":1700251049.7828493,"logger":"tls.handshake","msg":"matched certificate in cache","remote_ip":"179.248.199.129","remote_port":"41738","subjects":["example.com.br"],"managed":true,"expiration":1707696007,"hash":"ea19fd89505c068d122106178232f6eb15ef2bf84e7c14e05fd7b36704e884ba"}

this is the logs when i acess example.com.br

{"level":"debug","ts":1700251727.658523,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"localhost:3000","total_upstreams":1}
{"level":"debug","ts":1700251727.662508,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"localhost:3000","duration":0.003771091,"request":{"remote_ip":"179.248.199.129","remote_port":"60514","client_ip":"179.248.199.129","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/","headers":{"If-None-Match":["\"675edbd1fad8d194c9b8bdd8936aa7eafde4742e\""],"Dnt":["1"],"Sec-Ch-Ua-Platform":["\"Linux\""],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Ch-Ua-Mobile":["?0"],"Cookie":[],"X-Forwarded-For":["179.248.199.129"],"Sec-Fetch-Dest":["document"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-User":["?1"],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["example.com.br"],"Upgrade-Insecure-Requests":["1"],"Cache-Control":["max-age=0"],"Sec-Fetch-Site":["none"],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"headers":{"Date":["Fri, 17 Nov 2023 20:08:47 GMT"],"Connection":["keep-alive"],"Keep-Alive":["timeout=5"]},"status":304}
{"level":"debug","ts":1700251728.0063398,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"localhost:3000","total_upstreams":1}
{"level":"debug","ts":1700251728.0073783,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"localhost:3000","total_upstreams":1}
{"level":"debug","ts":1700251728.0153425,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"localhost:3000","duration":0.008844323,"request":{"remote_ip":"179.248.199.129","remote_port":"60514","client_ip":"179.248.199.129","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/static/js/main.f86b90ae.js","headers":{"If-None-Match":["\"7ef9bce34b2e113cb24e87956c431473ab1234e4\""],"Sec-Ch-Ua-Mobile":["?0"],"X-Forwarded-Proto":["https"],"Accept":["*/*"],"Sec-Fetch-Dest":["script"],"Referer":["https://example.com.br/"],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"Accept-Encoding":["gzip, deflate, br"],"Cookie":[],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Sec-Fetch-Site":["same-origin"],"Dnt":["1"],"X-Forwarded-For":["179.248.199.129"],"Sec-Ch-Ua-Platform":["\"Linux\""],"Sec-Ch-Ua":["\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""],"Sec-Fetch-Mode":["no-cors"],"X-Forwarded-Host":["example.com.br"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"headers":{"Date":["Fri, 17 Nov 2023 20:08:48 GMT"],"Connection":["keep-alive"],"Keep-Alive":["timeout=5"]},"status":304}
{"level":"debug","ts":1700251728.0186098,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"localhost:3000","duration":0.01113052,"request":{"remote_ip":"179.248.199.129","remote_port":"60514","client_ip":"179.248.199.129","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/static/css/main.2fdb139c.css","headers":{"If-None-Match":["\"5d1b9a5fe192186dcf7fe92733851aacac85ef4e\""],"X-Forwarded-Host":["example.com.br"],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""],"Sec-Fetch-Mode":["no-cors"],"Referer":["https://example.com.br/"],"X-Forwarded-For":["179.248.199.129"],"Dnt":["1"],"X-Forwarded-Proto":["https"],"Cookie":[],"Accept":["text/css,*/*;q=0.1"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Dest":["style"],"Sec-Ch-Ua-Platform":["\"Linux\""],"Sec-Fetch-Site":["same-origin"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"headers":{"Date":["Fri, 17 Nov 2023 20:08:48 GMT"],"Connection":["keep-alive"],"Keep-Alive":["timeout=5"]},"status":304}
{"level":"debug","ts":1700251728.3330727,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"localhost:3000","total_upstreams":1}
{"level":"debug","ts":1700251728.3355134,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"localhost:3000","duration":0.00232556,"request":{"remote_ip":"179.248.199.129","remote_port":"60514","client_ip":"179.248.199.129","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/static/css/main.2fdb139c.css.map","headers":{"If-None-Match":["\"9221d6f13c84b43d46e479cc8c47426c10bcf44b\""],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["example.com.br"],"Sec-Fetch-Dest":["empty"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"Cookie":[],"X-Forwarded-For":["179.248.199.129"],"Sec-Fetch-Site":["same-origin"],"Sec-Fetch-Mode":["no-cors"],"Accept-Encoding":["gzip, deflate, br"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"headers":{"Date":["Fri, 17 Nov 2023 20:08:48 GMT"],"Connection":["keep-alive"],"Keep-Alive":["timeout=5"]},"status":304}
{"level":"debug","ts":1700251728.3553576,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"localhost:3000","total_upstreams":1}
{"level":"debug","ts":1700251728.3581562,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"localhost:3000","duration":0.002677289,"request":{"remote_ip":"179.248.199.129","remote_port":"60514","client_ip":"179.248.199.129","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/static/js/main.f86b90ae.js.map","headers":{"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["example.com.br"],"Sec-Fetch-Site":["same-origin"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Dest":["empty"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Cookie":[],"X-Forwarded-For":["179.248.199.129"],"If-None-Match":["\"881432262a3dac5646aa8a2a7ae59b667a02bdec\""],"Sec-Fetch-Mode":["no-cors"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"headers":{"Date":["Fri, 17 Nov 2023 20:08:48 GMT"],"Connection":["keep-alive"],"Keep-Alive":["timeout=5"]},"status":304}
{"level":"debug","ts":1700251728.392117,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"localhost:3000","total_upstreams":1}
{"level":"debug","ts":1700251728.3947592,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"localhost:3000","duration":0.002511743,"request":{"remote_ip":"179.248.199.129","remote_port":"60514","client_ip":"179.248.199.129","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/favicon.ico","headers":{"If-None-Match":["\"00436ddaef62cb34fcdff9b1b967847585e591b2\""],"Sec-Fetch-Mode":["no-cors"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Mobile":["?0"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"Sec-Ch-Ua-Platform":["\"Linux\""],"Cookie":[],"Sec-Fetch-Site":["same-origin"],"Accept":["image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"],"X-Forwarded-Proto":["https"],"Dnt":["1"],"Sec-Fetch-Dest":["image"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""],"Referer":["https://example.com.br/"],"X-Forwarded-For":["179.248.199.129"],"X-Forwarded-Host":["example.com.br"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"headers":{"Keep-Alive":["timeout=5"],"Date":["Fri, 17 Nov 2023 20:08:48 GMT"],"Connection":["keep-alive"]},"status":304}
{"level":"debug","ts":1700251728.6783512,"logger":"events","msg":"event","name":"tls_get_certificate","id":"9daa9d74-fb46-4169-8c81-4d7f6635f2b0","origin":"tls","data":{"client_hello":{"CipherSuites":[64250,4865,4866,4867,49195,49199,49196,49200,52393,52392,49171,49172,156,157,47,53],"ServerName":"example.com.br","SupportedCurves":[27242,25497,29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[39578,772,771],"Conn":{}}}}
{"level":"debug","ts":1700251728.6785946,"logger":"tls.handshake","msg":"choosing certificate","identifier":"example.com.br","num_choices":1}
{"level":"debug","ts":1700251728.678625,"logger":"tls.handshake","msg":"default certificate selection results","identifier":"example.com.br","subjects":["example.com.br"],"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"ea19fd89505c068d122106178232f6eb15ef2bf84e7c14e05fd7b36704e884ba"}
{"level":"debug","ts":1700251728.6786356,"logger":"tls.handshake","msg":"matched certificate in cache","remote_ip":"179.248.199.129","remote_port":"60524","subjects":["example.com.br"],"managed":true,"expiration":1707696007,"hash":"ea19fd89505c068d122106178232f6eb15ef2bf84e7c14e05fd7b36704e884ba"}
{"level":"debug","ts":1700251728.9933133,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"localhost:3000","total_upstreams":1}
{"level":"debug","ts":1700251728.9960136,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"localhost:3000","duration":0.002542872,"request":{"remote_ip":"179.248.199.129","remote_port":"60524","client_ip":"179.248.199.129","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/manifest.json","headers":{"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"Dnt":["1"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""],"X-Forwarded-Host":["example.com.br"],"Referer":["https://example.com.br/"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["manifest"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["same-origin"],"Accept":["*/*"],"If-None-Match":["\"717324ae3a6ffc8e8ee7c1eadac62466184f027f\""],"Sec-Ch-Ua-Platform":["\"Linux\""],"X-Forwarded-For":["179.248.199.129"],"X-Forwarded-Proto":["https"],"Sec-Fetch-Mode":["cors"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"headers":{"Date":["Fri, 17 Nov 2023 20:08:48 GMT"],"Connection":["keep-alive"],"Keep-Alive":["timeout=5"]},"status":304}

This doesn’t make sense – your file_server does nothing here because reverse_proxy is higher on the directive order and will handle all requests (that aren’t /api*).

You’re also missing root which is typically necessary to tell Caddy where the static files are.

If you actually want to use file_server, then you need to use matchers to tell Caddy when to use the proxy and when to use the file server. Otherwise, remove file_server.

Your header matcher here is redundant, because this is already inside a site block example.com.br which itself is a host matcher.

I don’t understand what you mean. Please try re-explaining that.

I don’t see any access logs in here, because you didn’t enable the log directive within your site. You only configured logs globally which doesn’t enable access logs, it only changes where Caddy’s default logger writes out its runtime logs.

All this is showing is requests for static assets to your proxy upstream which respond with status 304 Not Modified because your client (browser) already has the assets cached, so there’s no need to respond with the content. This all looks normal.

i made my frontend using react, i think i dont need the file_server but the use of root * /app/build is mandatory?

this is my new config

{
    debug
    log {
        output file /var/log/caddy/access.log {
            format json
        }
    }

}

baixarvideotiktok.com.br {
    encode zstd gzip
    reverse_proxy localhost:3000

     log {
        output file /var/log/caddy/domain_access.log
        format json
    }

    route /api* {
        request_body {
            max_size 1MB
        }

        # api acess only from server ip and domain
        @serverIpOnly {
            remote_ip 24.199.107.244
        }

        handle @serverIpOrDomain {
            # only allow requests that match the specified criteria
            encode zstd gzip
            reverse_proxy localhost:3001

        }

        handle {
            # deny another requests
            respond "Forbidden" 403
        }
    }
}

this is the log inside the domain example.com.br

I want only the requests made by the frontend hosted on my server to be accepted when accessing the /api* route

{"level":"info","ts":1700512469.6564913,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"179.248.201.76","remote_port":"41184","client_ip":"179.248.201.76","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/","headers":{"Sec-Ch-Ua-Mobile":["?0"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Sec-Fetch-Site":["none"],"Sec-Fetch-Mode":["navigate"],"Cache-Control":["max-age=0"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Dest":["document"],"If-None-Match":["\"675edbd1fad8d194c9b8bdd8936aa7eafde4742e\""],"Sec-Ch-Ua":["\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""],"Dnt":["1"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"Cookie":[],"Sec-Ch-Ua-Platform":["\"Linux\""],"Sec-Fetch-User":["?1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"bytes_read":0,"user_id":"","duration":0.003652722,"size":0,"status":304,"resp_headers":{"Server":["Caddy"],"Alt-Svc":["h3=\":443\"; ma=2592000"],"Date":["Mon, 20 Nov 2023 20:34:29 GMT"]}}
{"level":"info","ts":1700512469.9740345,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"179.248.201.76","remote_port":"41184","client_ip":"179.248.201.76","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/static/js/main.f86b90ae.js","headers":{"If-None-Match":["\"7ef9bce34b2e113cb24e87956c431473ab1234e4\""],"Sec-Ch-Ua-Mobile":["?0"],"Accept":["*/*"],"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Site":["same-origin"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""],"Dnt":["1"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Sec-Ch-Ua-Platform":["\"Linux\""],"Sec-Fetch-Dest":["script"],"Referer":["https://example.com.br/"],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"Cookie":[]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"bytes_read":0,"user_id":"","duration":0.003908548,"size":0,"status":304,"resp_headers":{"Server":["Caddy"],"Alt-Svc":["h3=\":443\"; ma=2592000"],"Date":["Mon, 20 Nov 2023 20:34:29 GMT"]}}
{"level":"info","ts":1700512469.977021,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"179.248.201.76","remote_port":"41184","client_ip":"179.248.201.76","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/static/css/main.2fdb139c.css","headers":{"Sec-Ch-Ua-Platform":["\"Linux\""],"Accept":["text/css,*/*;q=0.1"],"Sec-Fetch-Mode":["no-cors"],"If-None-Match":["\"5d1b9a5fe192186dcf7fe92733851aacac85ef4e\""],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"Dnt":["1"],"Sec-Fetch-Site":["same-origin"],"Accept-Encoding":["gzip, deflate, br"],"Cookie":[],"Sec-Fetch-Dest":["style"],"Referer":["https://example.com.br/"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"bytes_read":0,"user_id":"","duration":0.006494578,"size":0,"status":304,"resp_headers":{"Server":["Caddy"],"Alt-Svc":["h3=\":443\"; ma=2592000"],"Date":["Mon, 20 Nov 2023 20:34:29 GMT"]}}
{"level":"info","ts":1700512470.3191798,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"179.248.201.76","remote_port":"41184","client_ip":"179.248.201.76","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/favicon.ico","headers":{"Sec-Fetch-Mode":["no-cors"],"Sec-Fetch-Dest":["image"],"Accept-Encoding":["gzip, deflate, br"],"If-None-Match":["\"00436ddaef62cb34fcdff9b1b967847585e591b2\""],"Accept":["image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"],"Sec-Ch-Ua-Mobile":["?0"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""],"Referer":["https://example.com.br/"],"Cookie":[],"Dnt":["1"],"Sec-Fetch-Site":["same-origin"],"Sec-Ch-Ua-Platform":["\"Linux\""]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"bytes_read":0,"user_id":"","duration":0.004858176,"size":0,"status":304,"resp_headers":{"Server":["Caddy"],"Alt-Svc":["h3=\":443\"; ma=2592000"],"Date":["Mon, 20 Nov 2023 20:34:30 GMT"]}}
{"level":"info","ts":1700512470.3209465,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"179.248.201.76","remote_port":"51710","client_ip":"179.248.201.76","proto":"HTTP/2.0","method":"GET","host":"example.com.br","uri":"/manifest.json","headers":{"Sec-Ch-Ua":["\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""],"Dnt":["1"],"Sec-Fetch-Site":["same-origin"],"Sec-Fetch-Mode":["cors"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"Accept":["*/*"],"Sec-Ch-Ua-Mobile":["?0"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Sec-Fetch-Dest":["manifest"],"Referer":["https://example.com.br/"],"If-None-Match":["\"717324ae3a6ffc8e8ee7c1eadac62466184f027f\""],"Sec-Ch-Ua-Platform":["\"Linux\""]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"bytes_read":0,"user_id":"","duration":0.005272132,"size":0,"status":304,"resp_headers":{"Server":["Caddy"],"Alt-Svc":["h3=\":443\"; ma=2592000"],"Date":["Mon, 20 Nov 2023 20:34:30 GMT"]}}
{"level":"error","ts":1700512488.9388232,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"179.248.201.76","remote_port":"41184","client_ip":"179.248.201.76","proto":"HTTP/2.0","method":"POST","host":"example.com.br","uri":"/api/baixar-video","headers":{"Cookie":[],"Accept":["application/json, text/plain, */*"],"Content-Type":["application/json"],"Dnt":["1"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Linux\""],"Sec-Fetch-Dest":["empty"],"Referer":["https://example.com.br/"],"Origin":["https://example.com.br"],"Content-Length":["125"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""],"Accept-Language":["pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"],"Sec-Fetch-Site":["same-origin"],"Sec-Fetch-Mode":["cors"],"Accept-Encoding":["gzip, deflate, br"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com.br"}},"bytes_read":0,"user_id":"","duration":0.000153801,"size":9,"status":403,"resp_headers":{"Server":["Caddy"],"Alt-Svc":["h3=\":443\"; ma=2592000"],"Content-Type":["text/plain; charset=utf-8"]}}

You’d need to set root if you use file_server, you don’t need it otherwise.

See here for an example that might be relevant for your case:

When you say “frontend”, do you mean your client-side javascript? If so then it’s impossible to do that by remote IP because the request is coming from the user’s browser. Unless you know exactly a list of IPs you want to allow to use your app (and block everyone else), that doesn’t make sense.

It’s inherently impossible to prevent anyone from making requests to an API, when you also want it to be used by a browser client. Those overlap on a Venn diagram, they can’t be separated.

You should use authentication to only allow particular users (with cookies or JWT tokens for example).