> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How to manage the `.well-known` directory on Upsun and Platform.sh

export const PostMeta = ({data = {}}) => {
  const {author, date, image} = data;
  const authors = Array.isArray(author) ? author : author ? [author] : [];
  const resolveAuthor = slug => {
    const entry = AUTHOR_MAP[slug] || ({});
    const name = entry.name || slug;
    const github = entry.github || null;
    const linkedin = entry.linkedin || null;
    const url = github ? `https://github.com/${github}` : linkedin || null;
    const avatarUrl = github ? `https://github.com/${github}.png?size=64` : null;
    return {
      name,
      url,
      avatarUrl
    };
  };
  const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }) : null;
  if (!image && authors.length === 0 && !formattedDate) return null;
  const AUTHOR_MAP = {
    "aaron-collier": {
      "name": "Aaron Collier"
    },
    "aaron-dudenhofer": {
      "name": "Aaron Dudenhofer"
    },
    "aaron-porter": {
      "name": "Aaron Porter"
    },
    "adriaan-odendaal": {
      "name": "Adriaan Odendaal"
    },
    "ajmal": {
      "name": "Ajmal Siddiqui"
    },
    "akalipetis": {
      "name": "Antonis Kalipetis"
    },
    "alexander-varwijk": {
      "name": "Alexander Varwijk"
    },
    "alicia-bevilacqua": {
      "name": "Alicia Bevilacqua"
    },
    "amelie-deguerry": {
      "name": "Amelie Deguerry"
    },
    "anacidre": {
      "name": "Ana Cidre",
      "linkedin": "https://www.linkedin.com/in/ana-cidre"
    },
    "andoni": {
      "name": "Andoni Auzmendi"
    },
    "andrei-taranu": {
      "name": "Andrei (Alex) Taranu",
      "linkedin": "https://www.linkedin.com/in/andrei-alex-taranu/"
    },
    "andrew-baxter": {
      "name": "Andrew Baxter"
    },
    "andrew-melck": {
      "name": "Andrew Melck"
    },
    "antoine-crochet-damais": {
      "name": "Antoine Crochet Damais"
    },
    "augustin-delaporte": {
      "name": "Augustin Delaporte",
      "linkedin": "https://www.linkedin.com/in/augustindelaporte/"
    },
    "branislav-bujisic": {
      "name": "Branislav Bujisic"
    },
    "carl-smith": {
      "name": "Carl Smith"
    },
    "caroline-leroy": {
      "name": "Caroline Leroy"
    },
    "cati-mayer": {
      "name": "Cati Mayer"
    },
    "catplat": {
      "name": "C Trinkwon"
    },
    "ceelolulu": {
      "name": "Celeste van der Watt"
    },
    "chadwcarlson": {
      "name": "Chad Carlson",
      "github": "chadwcarlson",
      "linkedin": "https://www.linkedin.com/in/chadwcarlson"
    },
    "chris-ward": {
      "name": "Chris Ward"
    },
    "chris-yates": {
      "name": "Chris Yates"
    },
    "christian-sieber": {
      "name": "Christian Sieber"
    },
    "christopher-lockheardt": {
      "name": "Christopher Lockheardt"
    },
    "christopher-skene": {
      "name": "Christopher Skene"
    },
    "chuck-morgan": {
      "name": "Chuck Morgan"
    },
    "corey-dockendorf": {
      "name": "Corey Dockendorf"
    },
    "crell": {
      "name": "Crell"
    },
    "damz": {
      "name": "Damz"
    },
    "dan-morrison": {
      "name": "Dan Morrison"
    },
    "davidbonachera": {
      "name": "David Bonachera",
      "github": "davidbonachera",
      "linkedin": "https://www.linkedin.com/in/davidbonachera"
    },
    "dereliahmet1": {
      "name": "Ahmet Faruk Dereli"
    },
    "devicezero": {
      "name": "Jonas Kröger",
      "github": "devicezero",
      "linkedin": "https://www.linkedin.com/in/jonaskroeger/"
    },
    "doug-goldberg": {
      "name": "Doug Goldberg"
    },
    "duncan-naves": {
      "name": "Duncan Naves",
      "github": "duncannaves",
      "linkedin": "https://www.linkedin.com/in/duncan-naves-a94423aa"
    },
    "erika-bustamante": {
      "name": "Erika Bustamante"
    },
    "fabpot": {
      "name": "Fabien Potencier"
    },
    "flovntp": {
      "name": "Florent Huck",
      "github": "flovntp",
      "linkedin": "https://www.linkedin.com/in/florenthuck"
    },
    "fred-plais": {
      "name": "Fred Plais"
    },
    "gauthier-garnier": {
      "name": "Gauthier Garnier"
    },
    "gilzow": {
      "name": "Paul Gilzow"
    },
    "gmoigneu": {
      "name": "Guillaume Moigneu",
      "github": "gmoigneu",
      "linkedin": "https://www.linkedin.com/in/guillaumemoigneu/"
    },
    "gregqualls": {
      "name": "Greg Qualls"
    },
    "guguss": {
      "name": "Augustin Delaporte"
    },
    "haylee-millar": {
      "name": "Haylee Millar"
    },
    "ivana-kotur": {
      "name": "Ivana Kotur"
    },
    "jackrabbithanna": {
      "name": "Mark Hanna"
    },
    "jared-wright": {
      "name": "Jared Wright",
      "github": "jww-sh",
      "linkedin": "https://www.linkedin.com/in/jaredwaynewright"
    },
    "jessica-orozco": {
      "name": "Jessica Orozco"
    },
    "joey-stanford": {
      "name": "Joey Stanford"
    },
    "john-grubb": {
      "name": "John Grubb"
    },
    "jonas-kruger": {
      "name": "Jonas Kruger"
    },
    "kathryn-frazer": {
      "name": "Kathryn Frazer"
    },
    "kemiojo": {
      "name": "Kemi Elizabeth Ojogbede"
    },
    "kieronsambrook-smith": {
      "name": "Kieronsambrook Smith"
    },
    "laurent-arnoud": {
      "name": "Laurent Arnoud",
      "linkedin": "https://www.linkedin.com/in/laurent-arnoud-861b44121/"
    },
    "letoya-boyne": {
      "name": "Letoya Boyne"
    },
    "lolautruche": {
      "name": "Jérôme Vieilledent"
    },
    "lyly-lepinay": {
      "name": "Lyly Lepinay"
    },
    "manauwar-alam": {
      "name": "Manauwar Alam"
    },
    "marc-antoine-porri": {
      "name": "Marc Antoine Porri"
    },
    "maria-antinkaapo": {
      "name": "Maria Antinkaapo"
    },
    "maria-de-anton": {
      "name": "Maria De Anton"
    },
    "mark-dorison": {
      "name": "Mark Dorison"
    },
    "markus-hausammann": {
      "name": "Markus Hausammann"
    },
    "mary-thomas": {
      "name": "Mary Thomas"
    },
    "mathias-bolt-lesniak": {
      "name": "Mathias Bolt Lesniak"
    },
    "mathieu-strauch": {
      "name": "Mathieu Strauch"
    },
    "matthias-van-woensel": {
      "name": "Matthias Van Woensel",
      "linkedin": "https://www.linkedin.com/in/matthias-van-woensel-267a069"
    },
    "michael-sharp": {
      "name": "Michael Sharp"
    },
    "mupsi": {
      "name": "Marine Gandy"
    },
    "natalie-harper": {
      "name": "Natalie Harper"
    },
    "ngommenginger": {
      "name": "Nicolas Gommenginger",
      "linkedin": "https://www.linkedin.com/in/nicolas-gommenginger"
    },
    "nicholas-bennison": {
      "name": "Nicholas Bennison"
    },
    "nicholas-vahalik": {
      "name": "Nicholas Vahalik"
    },
    "nick-hardiman": {
      "name": "Nick Hardiman"
    },
    "nickanderegg": {
      "name": "Nickanderegg"
    },
    "nicolas-grekas": {
      "name": "Nicolas Grekas",
      "github": "nicolas-grekas",
      "linkedin": "https://www.linkedin.com/in/nicolasgrekas/"
    },
    "niti-malwade": {
      "name": "Niti Malwade"
    },
    "opensocialteam": {
      "name": "Opensocialteam"
    },
    "ori-pekelman": {
      "name": "Ori Pekelman"
    },
    "otavio-santana": {
      "name": "Otavio Santana"
    },
    "palwandi": {
      "name": "Pawan Alwandi",
      "github": "pawpy",
      "linkedin": "https://www.linkedin.com/in/pawanalwandi"
    },
    "patrick-boest": {
      "name": "Patrick Boest"
    },
    "patrick-dawkins": {
      "name": "Patrick Dawkins",
      "github": "pjcdawkins",
      "linkedin": "https://www.linkedin.com/in/patrickdawkins"
    },
    "patrick-klima": {
      "name": "Patrick Klima"
    },
    "pjcdawkins": {
      "name": "Pjcdawkins"
    },
    "prineet-kaurbhurji": {
      "name": "Prineet Kaurbhurji"
    },
    "quentin-sinig": {
      "name": "Quentin Sinig"
    },
    "ralt": {
      "name": "Florian Margaine",
      "github": "ralt",
      "linkedin": "https://www.linkedin.com/in/florian-margaine-43971136"
    },
    "ramanathanramakrishnamurthy": {
      "name": "Ramanathanramakrishnamurthy"
    },
    "remi-lejeune": {
      "name": "Rémi Lejeune"
    },
    "ribel": {
      "name": "Taras Kruts"
    },
    "robert-douglass": {
      "name": "Robert Douglass"
    },
    "rudy-weber": {
      "name": "Rudy Weber"
    },
    "ryan-hicks": {
      "name": "Ryan Hicks"
    },
    "sabri-helal": {
      "name": "Sabri Helal"
    },
    "savannah-bergeron": {
      "name": "Savannah Bergeron"
    },
    "shannon-vettes": {
      "name": "Shannon Vettes"
    },
    "shawn-ogasawara": {
      "name": "Shawn Ogasawara",
      "linkedin": "https://www.linkedin.com/in/shawn-ogasawara-83a9a0/"
    },
    "shawna-spoor": {
      "name": "Shawna Spoor"
    },
    "shedrack-akintayo": {
      "name": "Shedrack Akintayo"
    },
    "simon-ruggier": {
      "name": "Simon Ruggier"
    },
    "sophie-van-der-kindere": {
      "name": "Sophie Van Der Kindere"
    },
    "stefanos-thampis": {
      "name": "Stefanos Thampis"
    },
    "stephen-weinberg": {
      "name": "Stephen Weinberg"
    },
    "sukhman-virk": {
      "name": "Sukhman Virk"
    },
    "sumaira-nazir": {
      "name": "Sumaira Nazir"
    },
    "sumer": {
      "name": "Sümer Cip"
    },
    "syed-raza": {
      "name": "Syed Raza"
    },
    "tamara-bacchia": {
      "name": "Tamara Bacchia"
    },
    "tara-arnold": {
      "name": "Tara Arnold"
    },
    "theosakamg": {
      "name": "Mickael Gaillard",
      "github": "theosakamg"
    },
    "thomasdiluccio": {
      "name": "Thomas di Luccio"
    },
    "tim-anderson": {
      "name": "Tim Anderson"
    },
    "tom-helmer-hansen": {
      "name": "Tom Helmer Hansen"
    },
    "tylermills": {
      "name": "Tyler Mills"
    },
    "upsun": {
      "name": "Upsun"
    },
    "veronika-tolkachova": {
      "name": "Veronika Tolkachova",
      "linkedin": "https://www.linkedin.com/in/veronika-tolkachova-169167a2"
    },
    "vince-parker": {
      "name": "Vince Parker"
    },
    "vinnie-russo": {
      "name": "Vincenzo Russo"
    },
    "vrobert78": {
      "name": "Vincent Robert",
      "github": "vrobert78",
      "linkedin": "https://www.linkedin.com/in/vincent-robert-498a883"
    },
    "yuriy-babenko": {
      "name": "Yuriy Babenko"
    },
    "yuriy-gerasimov": {
      "name": "Yuriy Gerasimov"
    }
  };
  return <div className="post-meta">
      {(authors.length > 0 || formattedDate) && <div className="post-meta-info">
          {authors.length > 0 && <div className="post-meta-authors">
              {authors.map(slug => {
    const {name, url, avatarUrl} = resolveAuthor(slug);
    const inner = <>
                    {avatarUrl && <img src={avatarUrl} alt={name} className="post-meta-avatar" />}
                    <span className="post-meta-author-name">{name}</span>
                  </>;
    return url ? <a key={slug} href={url} target="_blank" rel="noopener noreferrer" className="post-meta-author">
                    {inner}
                  </a> : <span key={slug} className="post-meta-author">{inner}</span>;
  })}
            </div>}
          {authors.length > 0 && formattedDate && <span className="post-meta-separator" aria-hidden="true">·</span>}
          {formattedDate && <span className="post-meta-date">{formattedDate}</span>}
        </div>}
      {image && <img src={image} alt="" className="post-meta-image" aria-hidden="true" />}
    </div>;
};

<PostMeta data={{ author: ["gilzow"], date: "2025-05-26T10:00:00+00:00", image: "/images/posts/how-tos/handling-well-known-directory/handle-well-known.webp" }} />

## Introduction

If you have ever tried to enable
[Apple Pay on a website](https://developer.apple.com/documentation/applepaywebmerchantregistrationapi/preparing-merchant-domains-for-verification),
or needed to verify you control a domain to generate a Let's Encrypt certificate, or noticed numerous requests to
`well-known/traffic-advice` in your access log, then you've probably found yourself working with the `.well-known`
directory at the root of your site. As defined in [RFC 8615](https://datatracker.ietf.org/doc/html/rfc8615),
`.well-known` is a special, standardized location at the root of a website that serves as a place where applications,
services, or security protocols can find important metadata or configuration files.

In the examples earlier, Apple Pay requests the file `apple-developer-merchantid-domain-association` inside the
`.well-known` directory in order to
[verify your merchant domain](https://developer.apple.com/documentation/applepaywebmerchantregistrationapi/preparing-merchant-domains-for-verification).\
Let's Encrypt uses `.domain-verification` inside `/.well-known/acme-challenge` in order to
[verify domain control](https://letsencrypt.org/docs/challenge-types/#http-01-challenge) before generating a
certificate. The Chrome browser retrieves `traffic-advice` from inside `.well-known`  in order to determine if the
[site allows pre-fetching](https://buettner.github.io/private-prefetch-proxy/traffic-advice.html), and if so, how much.

But this is just the tip of the iceberg. There are
[numerous](https://www.iana.org/assignments/well-known-uris/well-known-uris.xhtml)
[items](https://en.wikipedia.org/wiki/Well-known_URI#List_of_well-known_URIs) that can be placed inside
the `.well-known` directory. And as the number of types of applications, services, and protocols continues to grow, so
will the number of files that will be requested from this "well known location".

## The challenge

You may be thinking:

> "Can't I just create a `.well-known` directory in my repository, add my files, commit, push to
> Platform.sh/Upsun and be done?"

Yes... and no. The challenge is some of the files that you might need to place
in `.well-know` are extension-less (e.g. `apple-app-site-association` and `traffic-advice`), even though they contain
specific types of data (json in the two previous examples). If you place an extension-less in your project, the web
server is unable to determine the mime type and defaults to `content-type: application/octet-stream`

```bash {filename="Terminal"} theme={null}
❯ curl -I https://example.com/.well-known/apple-app-site-association
HTTP/2 200
accept-ranges: bytes
cache-control: no-cache
content-type: application/octet-stream
```

So then how can we configure our project to contain extension-less files but also serve the correct mime-type? Luckily,
we have a couple of options to utilize to solve this challenge.

### Option 1 - Rewrite the Request

The first method is to include the proper extension on the file so the web server knows how to set the correct mime type
, and then add a rewrite rule in our locations for requests to the extension-less version that "pass(es)thru" to our
version with the extension.

<Note>
  **Please Note**: After each change below, make sure you `git add`, and `git commit` your changes and then push the
  changes to your project.
</Note>

First, add the file to the `.well-known` directory *with the extension* in your project:

```bash {filename="Terminal"} theme={null}
❯ tree web/.well-known/
web/.well-known/
└── apple-app-site-association.json

```

Next we'll adjust the root (`/`) location in our `.platform.app.yaml` (Platform.sh) or `.upsun/config.yaml` (Upsun)
configuration file to include the [rule](https://docs.platform.sh/create-apps/app-reference/single-runtime-image.html#rules):

```yaml theme={null}
  locations:
    "/":
      passthru: "/index.php"
      root: "web"
      rules:
        '^/.well-known/apple-app-site-association$':
           allow: true
           passthru: '/.well-known/apple-app-site-association.json'
```

Now when a request is made for [https://example.com/.well-know/apple-app-site-association](https://example.com/.well-know/apple-app-site-association) the web server will rewrite
the request to `web/.well-known/apple-app-site-association.json`, and since it know the file is json, will return the
correct mime type:

```bash {filename="Terminal"} theme={null}
❯ curl -I https://example.com/.well-known/apple-app-site-association
HTTP/2 200
accept-ranges: bytes
cache-control: no-cache
content-type: application/json
```

### Option 2 - Manually set the Header Response

The second option is when you need even more flexibility in controlling the mime type that is returned. With the example
of `traffic-advice` earlier, not only is the request for an extension-less file, Chrome *expects* the mime-type to be
a specific mime type of
[`application/trafficadvice+json`](https://buettner.github.io/private-prefetch-proxy/traffic-advice.html#iana-mime-type).
If not, Chrome will reject it. It is entirely possible that other services also require a specific mime type in the
server response, or others may be added in the future with similar requirements. To tackle this challenge we'll take a
similar approach of using rules, but take advantage of some additional properties that are available in the rules
section.

Just like [Option 1](#option-1---rewrite-the-request), go ahead and add your file. Given we're going to manually set the
header response for the mime type, you can leave the file extension-less.

```bash {filename="Termninal"} theme={null}
❯ tree web/.well-known/
web/.well-known/
├── apple-app-site-association.json
└── traffic-advice

```

Without adding the rules, a request to the `traffic-advice` will return the wrong mime type causing Chrome to reject the
response:

```bash {filename="Terminal"} theme={null}
❯ curl -I https://example.com/.well-known/traffic-advice
HTTP/2 200
accept-ranges: bytes
cache-control: no-cache
content-type: application/octet-stream
```

<Note>
  **Please Note**: Since I'm now in need of multiple rules for a single location, I've added a location specifically for
  `.well-known` to my `web.locations`. This allows me to shorten my rules definitions as I no longer have to include the
  full paths.
</Note>

Let's then adjust our location in our `.platform.app.yaml` (Platform.sh) or `.upsun/config.yaml` (Upsun)
configuration file to respond with a [custom header](https://docs.platform.sh/create-apps/web/custom-headers.html) of
`application/trafficadvice+json` when the `traffic-advice` file is requested:

```yaml theme={null}
  locations:
    "/":
      passthru: "/index.php"
      root: "web"
    "/.well-known":
      root: "web/.well-known"
      rules:
        'apple-app-site-association':
          allow: true
          passthru: '/.well-known/apple-app-site-association.json'
        'traffic-advice':
          headers:
            Content-Type: application/trafficadvice+json
```

After pushing our change, now when something requests our `traffic-advice` file, the server not only responds with the
contents of the file, but is able to send the correct mime type:

```bash {filename="Terminal"} theme={null}
❯ curl -I https://example.com/.well-known/traffic-advice
HTTP/2 200
accept-ranges: bytes
cache-control: no-cache
content-type: application/trafficadvice+json
```

### Option 3 - Use a Mount

If desired, or you don't want to include these types of files in your repository, you could
[create a mount](https://docs.upsun.com/create-apps/app-reference/single-runtime-image.html#mounts) for `.well-known`,
[upload the needed files](https://docs.upsun.com/development/file-transfer.html#transfer-a-file-to-a-mount), and then
use any combination of the rules with `passthru` or custom headers to serve those files exactly as needed.

## Conclusion

Platform.sh and Upsun provide exceptional flexibility when configuring your projects, enabling developers to not only
fine-tune how extension-less files are served while maintaining correct MIME types, but also any other unexpected item
in your day-to-day. Whether leveraging rewrite rules to map requests to explicitly defined files, customizing response
headers for specialized formats, or accommodating future services with unique demands, by offering multiple
configuration strategies, Platform.sh and Upsun make it easy to scale and maintain high-performance applications without
sacrificing control over crucial web protocols.

***
