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

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

May 26, 2025· Paul Gilzow
Paul Gilzow
·Reading time: 5 minutes

Introduction

If you have ever tried to enable Apple Pay on a website, 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, .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.
Let’s Encrypt uses .domain-verification inside /.well-known/acme-challenge in order to verify domain control before generating a certificate. The Chrome browser retrieves traffic-advice from inside .well-known in order to determine if the site allows pre-fetching, and if so, how much.

But this is just the tip of the iceberg. There are numerous items 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

Terminal
❯ 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.

💡
Please Note: After each change below, make sure you git add, and git commit your changes and then push the changes to your project.

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

Terminal
❯ 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:

  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 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:

Terminal
❯ 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. 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, 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.

Termninal
❯ 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:

Terminal
❯ curl -I https://example.com/.well-known/traffic-advice
HTTP/2 200
accept-ranges: bytes
cache-control: no-cache
content-type: application/octet-stream
💡
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.

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 of application/trafficadvice+json when the traffic-advice file is requested:

  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:

Terminal
❯ 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 for .well-known, upload the needed files, 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.


Last updated on