{"id":13509679,"url":"https://github.com/letsencrypt/pebble","last_synced_at":"2025-05-13T21:12:39.331Z","repository":{"id":17167762,"uuid":"74700504","full_name":"letsencrypt/pebble","owner":"letsencrypt","description":"A miniature version of Boulder, Pebble is a small RFC 8555 ACME test server not suited for a production certificate authority.","archived":false,"fork":false,"pushed_at":"2025-05-07T14:16:20.000Z","size":5206,"stargazers_count":673,"open_issues_count":43,"forks_count":159,"subscribers_count":29,"default_branch":"main","last_synced_at":"2025-05-07T15:26:44.974Z","etag":null,"topics":["acme","acme-server","certificate-authority","https","letsencrypt","pki","rfc-8555","testing","x509"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/letsencrypt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-11-24T19:37:28.000Z","updated_at":"2025-05-07T14:12:25.000Z","dependencies_parsed_at":"2023-12-07T02:50:34.368Z","dependency_job_id":"90412bf1-fb91-4e24-8095-be485314d19f","html_url":"https://github.com/letsencrypt/pebble","commit_stats":{"total_commits":274,"total_committers":52,"mean_commits":5.269230769230769,"dds":0.6678832116788321,"last_synced_commit":"7c154bcd476a6e7e6c36221cec89b7027e019c04"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/letsencrypt%2Fpebble","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/letsencrypt%2Fpebble/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/letsencrypt%2Fpebble/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/letsencrypt%2Fpebble/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/letsencrypt","download_url":"https://codeload.github.com/letsencrypt/pebble/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254029008,"owners_count":22002284,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acme","acme-server","certificate-authority","https","letsencrypt","pki","rfc-8555","testing","x509"],"created_at":"2024-08-01T02:01:11.374Z","updated_at":"2025-05-13T21:12:34.310Z","avatar_url":"https://github.com/letsencrypt.png","language":"Go","funding_links":[],"categories":["Go","testing"],"sub_categories":[],"readme":"# Pebble\n\n[![Checks](https://github.com/letsencrypt/pebble/actions/workflows/checks.yml/badge.svg)](https://github.com/letsencrypt/pebble/actions/workflows/checks.yml)\n[![Tests](https://github.com/letsencrypt/pebble/actions/workflows/tests.yml/badge.svg)](https://github.com/letsencrypt/pebble/actions/workflows/tests.yml)\n\n[![Coverage Status](https://coveralls.io/repos/github/letsencrypt/pebble/badge.svg)](https://coveralls.io/github/letsencrypt/pebble)\n[![Go Report Card](https://goreportcard.com/badge/github.com/letsencrypt/pebble)](https://goreportcard.com/report/github.com/letsencrypt/pebble)\n\nA miniature version of [Boulder](https://github.com/letsencrypt/boulder), Pebble\nis a small [ACME](https://github.com/ietf-wg-acme/acme) test server not suited\nfor use as a production CA.\n\n## !!! WARNING !!!\n\n![WARNING](https://media.giphy.com/media/IT6kBZ1k5oEeI/giphy.gif)\n\nPebble is **NOT INTENDED FOR PRODUCTION USE**. Pebble is for **testing only**.\n\nBy design Pebble will drop all of its state between invocations and will\nrandomize keys/certificates used for issuance.\n\n## Goals\n\nPebble has several top level goals:\n\n1. Provide a simplified ACME testing front end\n1. Provide a test-bed for new and compatibility breaking ACME features\n1. Encourage ACME client best-practices\n1. Aggressively build in guardrails against non-testing usage\n\nPebble aims to address the need for ACME clients to have an easier to use,\nself-contained version of Boulder to test their clients against while developing\nACME v2 support. Boulder is multi-process, requires heavy dependencies (MariaDB,\ngRPC, etc), and is operationally complex to integrate with other projects.\n\nWhere possible Pebble aims to be a test-bed for new ACME protocol features that\ncan be used to inform later Boulder support. Pebble provides a way for Boulder\ndevelopers to test compatibility breaking changes more aggressively than is\nappropriate for Boulder.\n\nIn places where the ACME specification allows customization/CA choice Pebble\naims to make choices different from Boulder. For instance, Pebble changes the\npath structures for its resources and directory endpoints to differ from\nBoulder. The goal is to emphasize client specification compatibility and to\navoid \"over-fitting\" on Boulder and the Let's Encrypt production service.\n\nLastly, Pebble will enforce it's test-only usage by aggressively building in\nguardrails that make using it in a production setting impossible or very\ninconvenient. Pebble will not support non-volatile storage or persistence\nbetween executions. Pebble will also randomize keys/certificates used for\nissuance. Where possible Pebble will make decisions that force clients to\nimplement ACME correctly (e.g. randomizing `/directory` endpoint URLs to ensure\nclients are not hardcoding URLs.)\n\n## Limitations\n\nPebble is missing some ACME features (PRs are welcome!). It does not presently\nsupport pre-authorization or revoking a certificate issued by a different ACME\naccount by proving authorization of all of the certificate's domains.\n\nPebble does not perform all of the same input validation as Boulder. Some domain\nnames that would be rejected by Boulder/Let's Encrypt may work with Pebble.\n\nPebble does not enforce any rate limits. It is not presently an appropriate tool\nfor testing that your client handles Boulder/Let's Encrypt rate limits\ncorrectly.\n\n## Install\n\n1. [Set up Go](https://golang.org/doc/install)\n2. Add `~/go/bin` to your $PATH, or set `GOBIN` to a directory that is in your\n   $PATH already, so that `pebble` will be in your $PATH for easy execution.\n   - One way to do this is to add `export PATH=$PATH:$HOME/go/bin` to your `~/.profile`\n4. git clone https://github.com/letsencrypt/pebble/\n5. cd pebble\n6. go install ./cmd/pebble\n\n## Usage\n\n### Binary\n\nAssuming pebble is easily accessible in your $PATH:\n\n```bash\npebble -config ./test/config/pebble-config.json\n```\n\n(otherwise replace `pebble` with `~/go/bin/pebble` or `$GOBIN/pebble`)\n\nAfterwards you can access the Pebble server's ACME directory\nat `https://localhost:14000/dir`.\n\n### Docker\n\nPebble includes a [docker-compose](https://docs.docker.com/compose/) file that\nwill create a `pebble` instance that uses a `pebble-challtestsrv` instance for\nDNS resolution with the correct ports mapped to the host system.\n\nTo download and start the containers run:\n\n```\ndocker-compose up\n```\n\nAfterwards you can access the ACME API from your host machine at\n`https://localhost:14000/dir`, `pebble`'s management interface\nat `https://localhost:15000` and the `pebble-challtestsrv`'s management\ninterface at `http://localhost:8055`.\n\nTo get started you may want to update the `pebble-challtestsrv` mock DNS data\nwith a new default IPv4 address to use to respond to `A` queries from `pebble`:\n\n```\ncurl --request POST --data '{\"ip\":\"172.20.0.1\"}' http://localhost:8055/set-default-ipv4\n```\n\nSee the [pebble-challtestsrv\nREADME](https://github.com/letsencrypt/pebble/blob/master/cmd/pebble-challtestsrv/README.md)\nfor more information.\n\nIf you are running a one-off container for either `pebble` or\n`pebble-challtestsrv`, you will need to manually map ports.\n```\ndocker run -p 14000:14000 -p 15000:15000 ghcr.io/letsencrypt/pebble:latest\ndocker run -p 5001:5001 -p 5002:5002 -p 5003:5003 -p 8053:8053 -p 8055:8055 -p 8443:8443 ghcr.io/letsencrypt/pebble-challtestsrv:latest\n```\n\n#### Prebuilt Docker Images\n\nPebble releases are published as Docker images to the\n[Github Container Registry](https://github.com/orgs/letsencrypt/packages?repo_name=pebble)\n\nWith a docker-compose file:\n\n```yaml\nservices:\n pebble:\n  image: ghcr.io/letsencrypt/pebble:latest\n  command: -config /test/my-pebble-config.json\n  ports:\n    - 14000:14000  # ACME port\n    - 15000:15000  # Management port\n  environment:\n    - PEBBLE_VA_NOSLEEP=1\n  volumes:\n    - ./my-pebble-config.json:/test/my-pebble-config.json\n```\n\nWith a Docker command:\n\n```bash\ndocker run -p 14000:14000 -p 15000:15000 -e \"PEBBLE_VA_NOSLEEP=1\" ghcr.io/letsencrypt/pebble\n# or\ndocker run -p 14000:14000 -p 15000:15000 -e \"PEBBLE_VA_NOSLEEP=1\" --mount src=$(pwd)/my-pebble-config.json,target=/test/my-pebble-config.json,type=bind ghcr.io/letsencrypt/pebble -config /test/my-pebble-config.json\n```\n\n### Default validation ports\n\nTo make it easier to test ACME clients and run challenge response servers\nwithout root privileges Pebble defaults to validating ACME challenges using\nunprivileged high ports:\n\n* **Default HTTP-01 Port**: 5002\n* **Default TLS-ALPN-01 Port**: 5001\n\nThese ports can be changed by editing the `\"httpPort\"` and `\"tlsPort\"` values of\nthe Pebble `-config` file provided to `pebble`.\n\n### Strict Mode\n\nPebble's goal to aggressively support new protocol features and backwards\ncompatibility breaking changes is slightly at odds with its goal to provide\na simple, light-weight ACME test server for clients to use in integration tests.\nOn the one hand we want to introduce breaking changes quickly and use Pebble as\na test-bed for this. On the other we want to make sure we don't break client\nintegration tests using Pebble too often.\n\nAs a balance to meet these two needs Pebble supports a `-strict` flag. By\nrunning Pebble with `-strict false` changes known to break client compatibility\nare disabled.\n\nPresently we default `-strict` to false but this **will change in the future**.\nIf you are using Pebble for integration tests and favour reliability over\nlearning about breaking changes ASAP please explicitly run Pebble with `-strict\nfalse`.\n\n### DNS Server\n\nBy default Pebble uses the system DNS resolver, this may mean that caching causes\nproblems with DNS-01 validation. It may also mean that no DNSSEC validation is\nperformed.\nYou should configure your system's recursive DNS resolver according to your\nneeds or use the `-dnsserver` flag to define an address to a DNS server.\n\n```\npebble -dnsserver 10.10.10.10:5053\npebble -dnsserver 8.8.8.8:53\npebble -dnsserver :5053\n```\n\nYou may find it useful to set `pebble`'s `-dnsserver` to the address you used as\nthe `-dns01` argument when starting up a `pebble-challtestsrv` instance. This\nwill let you easily mock DNS data for Pebble. See the included\n`docker-compose.yml` and the [pebble-challtestsrv\nREADME](https://github.com/letsencrypt/pebble/blob/master/cmd/pebble-challtestsrv/README.md)\nfor more information.\n\n### Testing at full speed\n\nBy default Pebble will sleep a random number of seconds (from 0 to 15) between\nindividual challenge validation attempts. This ensures clients don't make\nassumptions about when the challenge is solved from the CA side by observing\na single request for a challenge response. Instead clients must poll the\nchallenge to observe the state since the CA may send many validation requests.\n\nTo test issuance \"at full speed\" with no artificial sleeps set the environment\nvariable `PEBBLE_VA_NOSLEEP` to `1`. E.g.\n\n`PEBBLE_VA_NOSLEEP=1 pebble -config ./test/config/pebble-config.json`\n\nThe maximal number of seconds to sleep can be configured by defining\n`PEBBLE_VA_SLEEPTIME`. It must be set to a positive integer.\n\n### Skipping Validation\n\nIf you want to avoid the hassle of having to stand up a challenge response\nserver for real HTTP-01, DNS-01 or TLS-ALPN-01 validation requests Pebble\nsupports a mode that always treats challenge validation requests as successful.\nBy default this mode is disabled and challenge validation is performed.\n\nTo have all challenge POST requests succeed without performing any validation\nrun:\n\n`PEBBLE_VA_ALWAYS_VALID=1 pebble`\n\n### Invalid Anti-Replay Nonce Errors\n\nThe `urn:ietf:params:acme:error:badNonce` error type is meant to be retry-able.\nWhen receiving this error a client should make a subsequent request to the\n`/new-nonce` endpoint (or use the nonce from the error response) to retry the\nfailed request, rather than quitting outright.\n\nExperience from Boulder indicates that many ACME clients do not gracefully retry\non invalid nonce errors. To help ensure future ACME clients are able to\ngracefully handle these errors by default **Pebble rejects 5% of all valid\nnonces as invalid**.\n\nThe percentage of valid nonces that are rejected can be configured using the\nenvironment variable `PEBBLE_WFE_NONCEREJECT`. E.g. to reject 90% of good nonces\nas invalid instead of 15% run:\n\n`PEBBLE_WFE_NONCEREJECT=90 pebble`\n\nTo **never** reject a valid nonce as invalid run:\n\n`PEBBLE_WFE_NONCEREJECT=0 pebble`\n\n### Object Reuse\n\nThe RFC allows for several objects to be re-used.\n\n**Clients should be prepared an ACME server may re-use any given object type, regardless of Pebble implementing a reuse policy for that object.**\n\nPebble and Boulder __may__ or __may not__ implement the same object re-use policies at any given time.  There exists an [ACME Implementation Details](https://github.com/letsencrypt/boulder/blob/main/docs/acme-implementation_details.md) document for Boulder which contains some information on how Boulder handles this.\n\n#### Order Reuse\n\nThe RFC allows ACME servers to reuse an Order. Pebble does not reuse Orders at this time; however Boulder does reuse Orders in at least one scenario:\n\n* If an Account requests a new Order that is identical to an already existing \"pending\" or \"ready\" Order for that same Account, the Order will be re-used.\n\n#### Authorization Reuse\n\nACME servers may choose to reuse authorizations from previous orders in new orders. ACME clients [should always check](https://tools.ietf.org/html/rfc8555#section-7.1.3) the status of a new order and its authorizations to confirm whether they need to respond to any challenges.\n\n#### Valid Authorization Reuse\n\n**Pebble will reuse valid authorizations in new orders, if they exist, 50% of the time**.\n\nThe percentage may be controlled with the environment variable `PEBBLE_AUTHZREUSE`, e.g. to always reuse authorizations:\n\n`PEBBLE_AUTHZREUSE=100 pebble`\n\n#### Pending Authorization Reuse\n\nPebble does not currently reuse Pending Authorizations across Orders, however other ACME servers - notably Boulder - will reuse Pending Authorizations. \n\n\n### Avoiding Client HTTPS Errors\n\nPebble is accessible over HTTPS only and uses a [test\ncertificate](test/certs/localhost/cert.pem) generated using a [test\nCA](test/certs/pebble.minica.pem) (See [the `test/certs/`\ndirectory](test/certs/README.md) for more information).\n\nSince the Pebble test CA isn't part of any default CA trust stores you must add\nthe [`test/certs/pebble.minica.pem`](test/certs/pebble.minica.pem) certificate\nto your client's trusted root configuration to avoid HTTPS errors. Your client\nshould offer a runtime option to specify a list of trusted root CAs.\n\n**IMPORTANT: Do not add the `pebble.minica.pem` CA to the system-wide trust\nstore or to any production systems/codebases. The private key for this CA is\nintentionally made [publicly available in this\nrepo](test/certs/pebble.minica.key.pem).**\n\n### Management interface\n\nIn order to ease the interaction of Pebble with testing systems, a specific HTTP\nmanagement interface is exposed on a different port than the ACME protocol,\nand offers several useful testing endpoints.\n\nThese endpoints are specific to Pebble and its internal behavior, and are not part\nof the RFC 8555 that defines the ACME protocol.\n\nThe management interface is configured by the `managementListenAddress` field in\n`pebble-config.json` that defines the address and the port on which the management\ninterface will listen on. Set `managementListenAddress` to an empty string or `null`\nto disable it.\n\nThe default configuration for this management interface as defined in\n`test/config/pebble-config.json` is to listen on any address on port 15000:\n\n```\n  \"managementListenAddress\": \"0.0.0.0:15000\",\n```\n\n#### CA Root and Intermediate Certificates\n\nNote that the CA's root and intermediate certificates are regenerated on every\nlaunch. They can be retrieved by a `GET` request to `https://localhost:15000/roots/0`\nand `https://localhost:15000/intermediates/0` respectively.\n\nYou might need the root certificate to verify the complete trust chain of\ngenerated certificates, for example in end-to-end tests.\n\nThe private keys of these certificates can also be retrieved by a `GET` request\nto `https://localhost:15000/root-keys/0` and `https://localhost:15000/intermediate-keys/0`\nrespectively.\n\n**IMPORTANT: Do not add Pebble's root or intermediate certificate to a trust\nstore that you use for ordinary browsing or that is used for non-testing\npurposes, since Pebble and its generated keys are not audited or held to the\nsame standards as the Let's Encrypt production CA and their keys. Moreover\nthese keys are exposed by Pebble and will be lost as soon as the process\nterminates: so they are not safe to use for anything other than testing.**\n\nIn case alternative root chains are enabled by setting `PEBBLE_ALTERNATE_ROOTS` to a\npositive integer, the root certificates for these can be retrieved by doing a `GET`\nrequest to `https://localhost:15000/roots/0`, `https://localhost:15000/root-keys/1`\n`https://localhost:15000/intermediates/2`, `https://localhost:15000/intermediate-keys/3`\netc. These endpoints also send `Link` HTTP headers for all alternative root and\nintermediate certificates and keys.\n\nThe length of certificate chains can be controlled using `PEBBLE_CHAIN_LENGTH`, which has\na default and minimum value of `1` (leaf + 1 intermediate). For higher values, Pebble will\ninclude extra intermediate certificates between the leaf and the root. Extra intermediate\ncertificates are *not* exposed via the management interface.\n\n#### Certificate Status\n\nThe certificate (in PEM format) and its revocation status can be queried by sending\na `GET` request to `https://localhost:15000/cert-status-by-serial/\u003cserial\u003e`, where\n`\u003cserial\u003e` is the hexadecimal representation of the certificate's serial number (no `0x` prefix).\nIt can be obtained via:\n\n    openssl x509 -in cert.pem -noout -serial | cut -d= -f2\n\nThe endpoint returns the information as a JSON object:\n\n    $ curl -ki https://127.0.0.1:15000/cert-status-by-serial/66317d2e02f5d3d6\n    HTTP/2 200\n    cache-control: public, max-age=0, no-cache\n    content-type: application/json; charset=utf-8\n    link: \u003chttps://127.0.0.1:15000/dir\u003e;rel=\"index\"\n    content-length: 1740\n    date: Fri, 12 Jul 2019 22:14:21 GMT\n\n    {\n       \"Certificate\": \"-----BEGIN CERTIFICATE-----\\nMIIEVz...tcw=\\n-----END CERTIFICATE-----\\n\",\n       \"Reason\": 4,\n       \"RevokedAt\": \"2019-07-13T00:13:20.418489956+02:00\",\n       \"Serial\": \"66317d2e02f5d3d6\",\n       \"Status\": \"Revoked\"\n    }\n\n\n### OCSP Responder URL\n\nPebble does not support the OCSP protocol as a responder and so does not set\nthe OCSP Responder URL in the issued certificates. However, if you setup a\nproper OCSP Responder run side by side with Pebble, you may want to set this URL.\nThis is possible by setting the field `ocspResponderURL` of the `pebble-config.json`\nconsummed by Pebble to a non empty string: in this case, this string will be use\nin the appropriate field of all issued certificates.\n\nFor instance, to have Pebble issue certificates that instruct a client to check the URL `http://127.0.0.1:4002`\nto retrieve the OCSP status of a certificate, run Pebble with a `pebble-config.json` that includes:\n\n```\n  \"ocspResponderURL\": \"http://127.0.0.1:4002\",\n```\n\n### Listing orders\n\nPebble has support for enumerating all orders for an ACME account object according to\n[RFC 8555, Section 7.1.2](https://tools.ietf.org/html/rfc8555#section-7.1.2.1). By default, three\norders are returned per page, to make it easy to test pagination. This number can be modified by\nsetting the `PEBBLE_WFE_ORDERS_PER_PAGE` environment variable to a positive integer. For example,\nto have 15 orders per page, run\n\n`PEBBLE_WFE_ORDERS_PER_PAGE=15 pebble`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fletsencrypt%2Fpebble","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fletsencrypt%2Fpebble","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fletsencrypt%2Fpebble/lists"}