A couple of days ago, I have tried the forward proxy plugin and it worked very well for my use case.
My complete sample Caddyfile is
:3128 {
forward_proxy {
ports 80 443
acl {
allow *.google.com
allow echo.free.beeceptor.com
deny all
}
}
}
This configuration allows to use the proxy for http and https to any google.com site or echo.free.beeceptor.com.
Some curl tests:
$ https_proxy=localhost:3128 http_proxy=localhost:3128 curl -v https://echo.free.beeceptor.com
* Uses proxy env variable https_proxy == 'localhost:3128'
* Trying 127.0.0.1:3128...
* Connected to localhost (127.0.0.1) port 3128 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to echo.free.beeceptor.com:443
> CONNECT echo.free.beeceptor.com:443 HTTP/1.1
> Host: echo.free.beeceptor.com:443
> User-Agent: curl/7.76.1
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 OK
< Server: Caddy
< Date: Sun, 15 Feb 2026 16:17:45 GMT
< Transfer-Encoding: chunked
* Ignoring Transfer-Encoding in CONNECT 200 response
<
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* ALPN, offering h2
* ALPN, offering http/1.1
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CONNECT phase completed!
* CONNECT phase completed!
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS header, Unknown (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Unknown (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Unknown (23):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.2 (IN), TLS header, Unknown (23):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Unknown (23):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=echo.free.beeceptor.com
* start date: Dec 31 02:39:53 2025 GMT
* expire date: Mar 31 02:39:52 2026 GMT
* subjectAltName: host "echo.free.beeceptor.com" matched cert's "echo.free.beeceptor.com"
* issuer: C=US; O=Let's Encrypt; CN=E7
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* TLSv1.2 (OUT), TLS header, Unknown (23):
* TLSv1.2 (OUT), TLS header, Unknown (23):
* TLSv1.2 (OUT), TLS header, Unknown (23):
* Using Stream ID: 1 (easy handle 0x5647aff70e60)
* TLSv1.2 (OUT), TLS header, Unknown (23):
> GET / HTTP/2
> Host: echo.free.beeceptor.com
> user-agent: curl/7.76.1
> accept: */*
>
* TLSv1.2 (IN), TLS header, Unknown (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.2 (IN), TLS header, Unknown (23):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
* TLSv1.2 (OUT), TLS header, Unknown (23):
* TLSv1.2 (IN), TLS header, Unknown (23):
* TLSv1.2 (IN), TLS header, Unknown (23):
* TLSv1.2 (IN), TLS header, Unknown (23):
< HTTP/2 200
< access-control-allow-origin: *
< alt-svc: h3=":443"; ma=2592000
< content-type: application/json
< date: Sun, 15 Feb 2026 16:17:46 GMT
< vary: Accept-Encoding
< via: 1.1 Caddy
<
* TLSv1.2 (IN), TLS header, Unknown (23):
{
"method": "GET",
"protocol": "https",
"host": "echo.free.beeceptor.com",
"path": "/",
"ip": "[2a01:239:326:1900::1]:37132",
"headers": {
"Host": "echo.free.beeceptor.com",
"User-Agent": "curl/7.76.1",
"Accept": "*/*",
"Via": "2.0 Caddy",
"Accept-Encoding": "gzip"
},
"parsedQueryParams": {}
* TLSv1.2 (IN), TLS header, Unknown (23):
* Connection #0 to host localhost left intact
}
$ https_proxy=localhost:3128 http_proxy=localhost:3128 curl -vI http://www.google.com
* Uses proxy env variable http_proxy == 'localhost:3128'
* Trying 127.0.0.1:3128...
* Connected to localhost (127.0.0.1) port 3128 (#0)
> HEAD http://www.google.com/ HTTP/1.1
> Host: www.google.com
> User-Agent: curl/7.76.1
> Accept: */*
> Proxy-Connection: Keep-Alive
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Cache-Control: private
Cache-Control: private
< Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-qL4V8ezgdCPnWvLBkgFC_Q' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-qL4V8ezgdCPnWvLBkgFC_Q' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
< Content-Type: text/html; charset=ISO-8859-1
Content-Type: text/html; charset=ISO-8859-1
< Date: Sun, 15 Feb 2026 16:21:53 GMT
Date: Sun, 15 Feb 2026 16:21:53 GMT
< Expires: Sun, 15 Feb 2026 16:21:53 GMT
Expires: Sun, 15 Feb 2026 16:21:53 GMT
< Reporting-Endpoints: default="//www.google.com/httpservice/retry/jserror?ei=ofKRabuFJ6SFxc8PqoWIkAw&cad=crash&error=Page%20Crash&jsel=1"
Reporting-Endpoints: default="//www.google.com/httpservice/retry/jserror?ei=ofKRabuFJ6SFxc8PqoWIkAw&cad=crash&error=Page%20Crash&jsel=1"
< Server: gws
Server: gws
< Set-Cookie: AEC=AaJma5taH1R8-Q5EVaXkL8oVqesbXemkr3RSYiPeO_P97YIcXLKjrhoJNWA; expires=Fri, 14-Aug-2026 16:21:53 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax
Set-Cookie: AEC=AaJma5taH1R8-Q5EVaXkL8oVqesbXemkr3RSYiPeO_P97YIcXLKjrhoJNWA; expires=Fri, 14-Aug-2026 16:21:53 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax
< Via: 1.1 caddy
Via: 1.1 caddy
< X-Frame-Options: SAMEORIGIN
X-Frame-Options: SAMEORIGIN
< X-Xss-Protection: 0
X-Xss-Protection: 0
<
* Connection #0 to host localhost left intac