SSL certificate problem: unable to get local issuer certificate

1. Output of caddy version:

caddy:alpine

2. How I run Caddy:

a. System environment:

Docker on Ubuntu 20.04

c. Service/unit/compose file:

version: '3.8'

# Docs ########################################################################
# https://github.com/nextcloud/docker

# Volumes #####################################################################
volumes:
  nextcloud_caddy_data:
    external: true
  nextcloud_data:
    external: true
  nextcloud_db_data:
    external: true

# Networks ####################################################################
networks:
  net:
    

# Services ####################################################################
services:
  # Caddy ---------------------------------------------------------------------
  caddy:
    image: caddy:alpine
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    environment:
      DOMAIN: "${DOMAIN:?DOMAIN not set}"
      ADMIN_EMAIL: "${ADMIN_EMAIL:?ADMIN_EMAIL not set}"
    networks:
      - net
    volumes:
      - nextcloud_caddy_data:/data
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile
    volumes_from:
      - app

  # Postgres ------------------------------------------------------------------
  db:
    image: postgres:${POSTGRES_VERSION:?POSTGRES_VERSION not set}
    restart: unless-stopped
    networks:
      - net
    volumes:
      - nextcloud_db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:?POSTGRES_USER not set} -d ${POSTGRES_DB:?POSTGRES_DB not set}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 5s
    environment:
      POSTGRES_DB: ${POSTGRES_DB:?POSTGRES_DB not set}
      POSTGRES_USER: ${POSTGRES_USER:?POSTGRES_USER not set}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD not set}
    # command:
    #   postgres
    #   -c max_connections=100
    #   -c shared_buffers=1GB
    #   -c effective_cache_size=3GB
    #   -c maintenance_work_mem=256MB
    #   -c checkpoint_completion_target=0.9
    #   -c wal_buffers=16MB
    #   -c default_statistics_target=100
    #   -c random_page_cost=1.1
    #   -c effective_io_concurrency=200
    #   -c work_mem=10485kB
    #   -c min_wal_size=2GB
    #   -c max_wal_size=8GB
    #   -c max_worker_processes=2
    #   -c max_parallel_workers_per_gather=1
    #   -c max_parallel_workers=2
    #   -c max_parallel_maintenance_workers=1

  # Redis ---------------------------------------------------------------------
  redis:
    image: redis:alpine
    networks:
      - net
    restart: unless-stopped

  # Nextcloud -----------------------------------------------------------------
  app:
    image: nextcloud:${NEXTCLOUD_VERSION:?NEXTCLOUD_VERSION not set}
    networks:
      - net
    restart: unless-stopped
    volumes:
      - nextcloud_data:/var/www/html
    environment:
      NEXTCLOUD_TRUSTED_DOMAINS: ${DOMAIN:?DOMAIN not set}
      NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER:?NEXTCLOUD_ADMIN_USER not set}
      NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD:?NEXTCLOUD_ADMIN_PASSWORD not set}
      REDIS_HOST: redis
      POSTGRES_HOST: db
      POSTGRES_DB: ${POSTGRES_DB:?POSTGRES_DB not set}
      POSTGRES_USER: ${POSTGRES_USER:?POSTGRES_USER not set}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD not set}
      PHP_MEMORY_LIMIT: '512M'
      PHP_UPLOAD_LIMIT: '16G'
      TZ: 'America/SaoPaulo'
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  # Cron ----------------------------------------------------------------------
  cron:
    image: nextcloud:${NEXTCLOUD_VERSION:?NEXTCLOUD_VERSION not set}
    networks:
      - net
    restart: unless-stopped
    volumes_from:
      - app
    entrypoint: /cron.sh
    depends_on:
      db:
        condition: service_healthy
      app:
        condition: service_started
      redis:
        condition: service_started

  # Collabora ---------------------------------------------------------------------
  collabora:
    image: collabora/code
    restart: unless-stopped
    networks:
      - net
    ports:
      - "9980:9980"
    volumes:
      - /etc/localtime:/etc/localtime
      - /etc/timezone:/etc/timezone
    environment:
      - username=admin
      - password=${COLLABORA_PASSWORD}
      - server_name=${DOMAIN:?DOMAIN not set}
      - dictionaries=en_US
      - extra_params=--o:ssl.enable=true --o:ssl.termination=false # Set SSL options
    cap_add:
      - MKNOD
    tty: true

d. My complete Caddy config:

{

  # SSL
  email {$ADMIN_EMAIL}
  acme_ca https://acme-staging-v02.api.letsencrypt.org/directory

  # Enable to see header set by upstream
  debug
}

{$DOMAIN} {


  # Static content
  root * /var/www/html
  file_server

  # PHP fast cgi
  php_fastcgi app:9000 {
    env front_controller_active true
  }

  # Redirects for DAV apps
  redir /.well-known/carddav /remote.php/carddav 301
  redir /.well-known/caldav /remote.php/caldav 301

  respond /.well-known/acme-challenge 404
  respond /.well-known/pki-validation 404

  # redir /.well-known/* /index.php/.well-known/webfinger 301
  # redir /.well-known/nodeinfo /index.php/.well-known/nodeinfo 301
  redir /.well-known/* /index.php{uri} 301

  # Headers
  header {
    # If staging acme_ca is enabled, this needs to be commented out!
    # Otherwise, it is not possible to add exception
    Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

    # More security hardening headers
    Referrer-Policy                   "no-referrer"
    X-Content-Type-Options            "nosniff"
    X-Download-Options                "noopen"
    X-Frame-Options                   "SAMEORIGIN"
    X-Permitted-Cross-Domain-Policies "none"
    X-Robots-Tag                      "none"
    X-XSS-Protection                  "1; mode=block"
    Permissions-Policy                "interest-cohort=()"

    # Remove X-Powered-By header, which is an information leak
    -X-Powered-By

    # Replace http with https in any Location header
    Location http:// https://
  }

  # Cache control
  @static {
    file
    path *.css *.js *.svg *.gif
  }

  header @static {
    Cache-Control "max-age=360"
  }

  @fonts {
    path /core/fonts
  }

  header @fonts {
    Cache-Control "max-age=604800"
  }

  # gzip encoding
  encode {
    gzip 4
    minimum_length 256

    match {
      header Content-Type text/*
      header Content-Type application/json*
      header Content-Type application/javascript*
      header Content-Type application/xhtml+xml*
      header Content-Type application/atom+xml*
      header Content-Type application/rss+xml*
      header Content-Type image/svg+xml*
      header Content-Type application/ld+json*
      header Content-Type application/manifest+json*
      header Content-Type application/vnd.geo+json*
      header Content-Type application/vnd.ms-fontobject*
      header Content-Type application/x-font-ttf*
      header Content-Type application/x-web-app-manifest+json*
      header Content-Type application/xml*
      header Content-Type font/opentype*
      header Content-Type image/bmp*
      header Content-Type image/x-icon*
      header Content-Type text/cache-manifest*
    }
  }

  @collabora {
              path /browser/* # Loleaflet is the client part of LibreOffice Online
              path /hosting/discovery # WOPI discovery URL
              path /hosting/capabilities # Show capabilities as json
              path /cool/* # Main websocket, uploads/downloads, presentations
              }

  reverse_proxy @collabora collabora:9980 {
    header_up Host "maraujo.rio.br"
    transport http {
      tls_insecure_skip_verify
    }
  }
  # .htaccess / data / config / ... shouldn't be accessible from outside
  @forbidden {
    path    /build/*
    path    /tests/*
    path    /.htaccess
    path    /data/*
    path    /config/*
    path    /db_structure
    path    /.xml
    path    /README
    path    /3rdparty/*
    path    /lib/*
    path    /templates/*
    path    /occ
    path    /console.php
    path    /autotest
    path    /issue
    path    /indie
    path    /db_
    path    /console
  }

  respond @forbidden 404
}

3. The problem I’m having:

Caddy is not able to assign the certificate to my domain, and I am not able to find out why.

4. Error messages and/or full log output:

{"level":"info","ts":1659116172.3981483,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"}
{"level":"warn","ts":1659116172.4000545,"msg":"Caddyfile input is not formatted; run the 'caddy fmt' command to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":2}
{"level":"info","ts":1659116172.401214,"logger":"admin","msg":"admin endpoint started","address":"tcp/localhost:2019","enforce_origin":false,"origins":["//[::1]:2019","//127.0.0.1:2019","//localhost:2019"]}
{"level":"info","ts":1659116172.4014409,"logger":"http","msg":"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}
{"level":"info","ts":1659116172.4014585,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1659116172.403151,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc00036b960"}
{"level":"info","ts":1659116172.4046707,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["maraujo.rio.br"]}
{"level":"info","ts":1659116172.4050379,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
{"level":"info","ts":1659116172.4051213,"msg":"serving initial configuration"}
{"level":"info","ts":1659116172.4051425,"logger":"tls","msg":"cleaning storage unit","description":"FileStorage:/data/caddy"}
{"level":"info","ts":1659116172.4052994,"logger":"tls","msg":"finished cleaning storage units"}
{"level":"info","ts":1659116172.4063444,"logger":"tls.obtain","msg":"acquiring lock","identifier":"maraujo.rio.br"}
{"level":"info","ts":1659116172.4073715,"logger":"tls.obtain","msg":"lock acquired","identifier":"maraujo.rio.br"}
{"level":"info","ts":1659116172.7562346,"logger":"tls.issuance.acme","msg":"waiting on internal rate limiter","identifiers":["maraujo.rio.br"],"ca":"https://acme-staging-v02.api.letsencrypt.org/directory","account":"dev@maraujo.rio.br"}
{"level":"info","ts":1659116172.7562716,"logger":"tls.issuance.acme","msg":"done waiting on internal rate limiter","identifiers":["maraujo.rio.br"],"ca":"https://acme-staging-v02.api.letsencrypt.org/directory","account":"dev@maraujo.rio.br"}
{"level":"info","ts":1659116172.9021325,"logger":"tls.issuance.acme.acme_client","msg":"trying to solve challenge","identifier":"maraujo.rio.br","challenge_type":"http-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
{"level":"info","ts":1659116173.2747335,"logger":"tls.issuance.acme","msg":"served key authentication","identifier":"maraujo.rio.br","challenge":"http-01","remote":"18.158.72.241:36052","distributed":false}
{"level":"info","ts":1659116173.8287554,"logger":"tls.issuance.acme","msg":"served key authentication","identifier":"maraujo.rio.br","challenge":"http-01","remote":"23.178.112.107:53656","distributed":false}
{"level":"info","ts":1659116174.0030735,"logger":"tls.issuance.acme","msg":"served key authentication","identifier":"maraujo.rio.br","challenge":"http-01","remote":"52.35.116.194:13908","distributed":false}
{"level":"info","ts":1659116174.2166562,"logger":"tls.issuance.acme.acme_client","msg":"validations succeeded; finalizing order","order":"https://acme-staging-v02.api.letsencrypt.org/acme/order/62634104/3416610204"}
{"level":"info","ts":1659116174.512387,"logger":"tls.issuance.acme.acme_client","msg":"successfully downloaded available certificate chains","count":1,"first_url":"https://acme-staging-v02.api.letsencrypt.org/acme/cert/fad197662a10617da715fbdc31efc77a8aa2"}
{"level":"info","ts":1659116174.5133917,"logger":"tls.obtain","msg":"certificate obtained successfully","identifier":"maraujo.rio.br"}
{"level":"info","ts":1659116174.5134177,"logger":"tls.obtain","msg":"releasing lock","identifier":"maraujo.rio.br"}
{"level":"error","ts":1659121960.7102084,"logger":"http.handlers.reverse_proxy","msg":"aborting with incomplete response","error":"http2: stream closed"}
{"level":"error","ts":1659124544.342186,"logger":"tls.issuance.acme","msg":"looking up info for HTTP challenge","host":"next.maraujo.rio.br","error":"no information found to solve challenge for identifier: next.maraujo.rio.br"}
{"level":"error","ts":1659124544.3422573,"logger":"tls.issuance.acme","msg":"looking up info for HTTP challenge","host":"next.maraujo.rio.br","error":"no information found to solve challenge for identifier: next.maraujo.rio.br"}

curl -v

*   Trying 140.82.13.234:443...
* TCP_NODELAY set
* Connected to maraujo.rio.br (140.82.13.234) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* 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 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: unable to get local issuer certificate
* Closing connection 0
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above

5. What I already tried:

I tried to configure the acme_server and tls internal inside the Caddyfile, but it didn’t work.

If you use the staging endpoint of Let’s Encrypt, then certificates it issues won’t be trusted. That looks to be working as intended.