1. The problem I’m having:
I am serving a FastAPI to send an email. I want to make my API public using Caddy. I have the FastAPI server running with uvicorn and everything works great when I send a POST to localhost:9000. However, I cannot get the API working from my public url and site served through caddy.
2. Error messages and/or full log output:
Successful API call
curl -vL -X POST http://localhost:9000/ \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=John Doe" \
-d "company=Acme Corp" \
-d "phone=1234567890" \
-d "email=johndoe@example.com" \
-d "availability=Monday to Friday"
Note: Unnecessary use of -X or --request, POST is already inferred.
* Host localhost:9000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:9000...
* connect to ::1 port 9000 from ::1 port 34196 failed: Connection refused
* Trying 127.0.0.1:9000...
* Connected to localhost (127.0.0.1) port 9000
> POST / HTTP/1.1
> Host: localhost:9000
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 104
>
< HTTP/1.1 200 OK
< date: Fri, 01 Nov 2024 16:04:17 GMT
< server: uvicorn
< content-length: 41
< content-type: application/json
<
* Connection #0 to host localhost left intact
{"message":"Form submitted successfully"}
Unsuccessful call from the public domain and path
curl -vL -X POST http://cleanmybuilding.co/submit-form \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=John Doe" \
-d "company=Acme Corp" \
-d "phone=1234567890" \
-d "email=johndoe@example.com" \
-d "availability=Monday to Friday"
Note: Unnecessary use of -X or --request, POST is already inferred.
* Host cleanmybuilding.co:80 was resolved.
* IPv6: (none)
* IPv4: 12.235.102.86
* Trying 12.235.102.86:80...
* Connected to cleanmybuilding.co (12.235.102.86) port 80
> POST /submit-form HTTP/1.1
> Host: cleanmybuilding.co
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 104
>
< HTTP/1.1 308 Permanent Redirect
< Connection: close
* Please rewind output before next send
< Location: https://cleanmybuilding.co/submit-form
< Server: Caddy
< Date: Fri, 01 Nov 2024 16:09:58 GMT
< Content-Length: 0
<
* Closing connection
* Clear auth, redirects to port from 80 to 443
* Issue another request to this URL: 'https://cleanmybuilding.co/submit-form'
* Host cleanmybuilding.co:443 was resolved.
* IPv6: (none)
* IPv4: 12.235.102.86
* Trying 12.235.102.86:443...
* Connected to cleanmybuilding.co (12.235.102.86) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /usr/lib/ssl/cert.pem
* CApath: /usr/lib/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
* subject: CN=cleanmybuilding.co
* start date: Nov 1 15:07:41 2024 GMT
* expire date: Jan 30 15:07:40 2025 GMT
* subjectAltName: host "cleanmybuilding.co" matched cert's "cleanmybuilding.co"
* issuer: C=US; O=Let's Encrypt; CN=E6
* SSL certificate verify ok.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
* Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://cleanmybuilding.co/submit-form
* [HTTP/2] [1] [:method: POST]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: cleanmybuilding.co]
* [HTTP/2] [1] [:path: /submit-form]
* [HTTP/2] [1] [user-agent: curl/8.5.0]
* [HTTP/2] [1] [accept: */*]
* [HTTP/2] [1] [content-type: application/x-www-form-urlencoded]
* [HTTP/2] [1] [content-length: 104]
> POST /submit-form HTTP/2
> Host: cleanmybuilding.co
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 104
>
< HTTP/2 404
< access-control-expose-headers: X-User-ID
< alt-svc: h3=":443"; ma=2592000
< content-type: application/json
< date: Fri, 01 Nov 2024 16:09:57 GMT
< server: Caddy
< server: uvicorn
< content-length: 22
<
* Connection #1 to host cleanmybuilding.co left intact
{"detail":"Not Found"}
Ive also tried curl with http://cleanmybuilding.co/submit-form/ and the same result happens
3. Caddy version:
caddy version
v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=
4. How I installed and ran Caddy:
Running from Caddyfile with caddy run
a. System environment:
Running Caddy on Ubuntu 24 Headless
b. FastAPI App:
# Brian Lesko
# FastAPI Email API
import sys
sys.path.append('/home/lesko')
from fastapi import FastAPI, Form
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware
from libemail import SimpleEmailSender
from libstyle import get_email_template, get_button
app = FastAPI()
# Allow only your domain
origins=["*"] # https://cleanmybuilding.co
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["POST"],
allow_headers=["*"],
)
# Define form data structure
class FormData(BaseModel):
name: str
company: str
phone: str
email: str
availability: str
@app.post("/")
async def submit_form(
name: str = Form(...),
company: str = Form(...),
phone: str = Form(...),
email: str = Form(...),
availability: str = Form(...)
):
# Construct email content
email_content = f"""\
Name: {name}
Company: {company}
Phone: {phone}
Email: {email}
Availability: {availability}
"""
# Initialize and send email using SimpleEmailSender
subject = "Quote Request"
content = f"""
<h1>Customer Quote Request</h1>
<h2>{name} with {company}is requesting a quote</h2>
<h3>Email: {email} Phone: {phone}</h3>
<h3>Availability: {availability}</h3>
<h3>Please follow up with the customer as soon as possible</h3>
"""
body = get_email_template(subject,content)
SimpleEmailSender(subject, body, 'email@mail.com').send_email()
return {"message": "Form submitted successfully"}
d. My complete Caddy config:
(basic-auth) {
route {
header X-User-ID {http.auth.user.id}
}
basic_auth {
email hashed-password
}
}
cleanmybuilding.co {
root * /home/lesko/public-website
file_server
header Access-Control-Expose-Headers "X-User-ID"
handle_path /employees/pto/request* {
import basic-auth
reverse_proxy 0.0.0.0:8502
}
handle_path /employees/pto/total* {
import basic-auth
reverse_proxy 0.0.0.0:8503
}
handle_path /employees/pto/approve* {
import basic-auth
reverse_proxy 0.0.0.0:8504
}
handle_path /employees/pto/calendar* {
import basic-auth
reverse_proxy 0.0.0.0:8505
}
handle_path /employees/pto/manage* {
import basic-auth
reverse_proxy 0.0.0.0:8506
}
handle_path /employees/supplies* {
import basic-auth
reverse_proxy 0.0.0.0:8507
}
handle_path /employees/query_sql* {
import basic-auth
reverse_proxy 0.0.0.0:8508
}
handle_path /employees/database* {
import basic-auth
reverse_proxy 0.0.0.0:8509
}
handle_path /employees/chat* {
import basic-auth
reverse_proxy 0.0.0.0:8510
}
handle_path /employees/supplies/approve* {
import basic-auth
reverse_proxy 0.0.0.0:8511
}
handle_path /employees/reports/manager* {
import basic-auth
reverse_proxy 0.0.0.0:8512
}
handle_path /employees* {
import basic-auth
reverse_proxy 0.0.0.0:8001
}
route /submit-form* {
reverse_proxy 0.0.0.0:9000
}
}