> ## 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.

# Route all the things

> Learn how the routing system works on Platform.sh, including its flexibility, multi-domain support, and routing configurations.

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: ["crell"], date: "2017-12-20", image: "/images/posts/unknown/route-all-the-things/route-all-the-things.webp" }} />

<Tip>
  This post was originally published on the Platform.sh blog and reflects information from the time of publication.
</Tip>

Platform.sh has always put a great value on customer flexibility. That flexibility at times can seem bewildering, however, as users have an ample supply of buttons and dials to to customize their application architecture, hosting, and workflow. In particular, the routing system built into every project offers an enormous amount of flexibility (single application, multi-application, microservice, arbitrary redirects, etc.), but how exactly does it work?

Since we've just added new functionality for users hosting many domains, let's take this opportunity to take a deep dive into the routing system.

Short version: You can now specify an `{all}` placeholder in `.platform/routes.yaml` that maps all incoming domains to the same application, making multi-domain applications much easier to manage.

Long version:

### How Platform.sh does routing

Every project on Platform.sh has a `master` environment for production, but it can also have many other environments for testing and development, potentially one for each Git branch. Each branch/environment has its own copy of the application, its own database server, and its own data, all on a series of lightweight containers.

Each environment also has its own "router", which is also a container. All requests to any application in your project go through this router.

But how does our infrastructure know for a given incoming request to which project/router container it should go?

That's where routing comes in, which is controlled from the `routes.yaml` file. There are actually two layers of routing involved, one "edge router" and one "environment router".

* The edge router maps incoming requests to a region to the right environment router.
* The environment router maps incoming requests to the right application container.

Both routers are updated every time you deploy to any branch, based on the information in the `routes.yaml` file and the domains configured in the project. Those get merged to produce essentially giant lookup tables in each router. (It's a bit more complex than that, but close enough for a blog.)

Suppose we look at the industry standard website, `https://example.com/`. Its `routes.yaml` file would most likely contain 2 standard entries:

```yaml theme={null}
https://{default}/:
  type: upstream
  upstream: app:http
https://www.{default}/:
  type: redirect
  to: https://{default}/

```

The project is also configured with a single domain, `example.com`. That means at deploy time on the `master` branch the above entries effectively turn into:

```yaml theme={null}
https://example.com/:
  type: upstream
  upstream: app:http
https://www.example.com/:
  type: redirect
  to: https://example.com/
http://example.com/:
  type: redirect
  to: https://example.com/
http://www.example.com/:
  type: redirect
  to: https://example.com/

```

That's right, four entries. By default, Platform.sh automatically creates a redirect from any HTTP route to its HTTPS equivalent. We're just doing our part to make the web more secure for everyone.

Two things happen with that list. First, the router container for that specific environment is created and the above list of domain instructions are turned into a configuration file for it. Three of the rules are just redirects, while the first entry tells router to proxy the incoming request to the application container named `app` in the same environment.

Second, there's two domains in the list: `www.example.com` and `example.com`. It doesn't matter what their configuration is; an entry is added to the edge router's lookup table that both of those domains should be forwarded to the router container for that environment. Our edge router is a custom, high-throughput proxy server that focuses on just one thing: Proxying incoming requests to the correct router container.

When a request comes in for example.com (like the request you send to view this page), the edge router looks up "example.com" in its table and finds that the request should go to a particular router container. It then proxies the request to that router. The router sees the request and that it should forward it to the application container with which it was deployed. The request then gets proxied to the application container where an Nginx/PHP-FPM setup is waiting for it, which sends back a response. (You could just as easily be using Ruby or Python or Go on the app container; everything else is exactly the same.)

Strictly speaking, that Nginx process on the application container is a third layer of "routing", and the one that offers end-users the most configuration. Depending on the [.platform.app.yaml](https://docs.platform.sh/overview/yaml/what-is-yaml.html) file, it could serve static files from disk, hand the request off to PHP-FPM or a Node.js process, add additional headers to the response, and many other things.

Of the three, only the router container does any caching, assuming its configuration and the HTTP headers of the response tell it to. (More on that another time.)

### Multiple environments

This multi-layer routing system offers an incredible amount of flexibility at surprisingly little overhead. In particular, it makes it possible for us to create an effectively infinite number of additional environments for your Git branches. Suppose we have an `update` branch where we are testing an update to one of the Drupal modules that powers this site. We obviously need a new domain for that environment, since `example.com` is already used by the production site. Our system generates that new domain dynamically based on the branch name and project. That's where the common "gibberish domain" for dev branches comes from: `$branch_id`-`$project_id`.`$region`.

On a dev branch, then, the exact same process happens as in production. The only difference is that instead of looking at the configured domain for the site (example.com), we use the generated domain. Otherwise the process is identical.

### Multiple applications

Platform.sh also supports multiple related applications in the same environment. That could be a front-end application and a backend API. It could be a micro-services setup. It could be a dynamic website and a queue worker in another language. It could be a static website with one directory a dynamic blog application. (Yep, you can route subdirectories to different applications in `routes.yaml`. Neat, huh?) Or whatever else works for your use case.

Let's pretend for a moment that we wanted to redesign our website to be a static main site with a WordPress blog at `blog.example.com`. We would modify our `routes.yaml` file like so:

```yaml theme={null}
https://{default}/:
  type: upstream
  upstream: site:http
https://www.{default}/:
  type: redirect
  to: https://{default}/
https://blog.{default}/:
  Type: upstream
  Upstream: blog:http

```

There's now 2 applications defined: `site` has whatever static site generator builds the main site, and `blog` is the WordPress blog. Both are in separate directories with their own `.platform.app.yaml` file.

Now on deploy, the edge router gets three entries (`example.com`, `www.example.com`, `blog.platform.sh`), all pointing at the router container for the environment. That router now has six entries:

* Three HTTP->HTTPS redirects
* One `www.example.com`->`example.com` redirect
* One `example.com`->`site` container proxy
* One `blog.example.com`->`blog` container proxy

We could of course have put the blog at `example.com/blog` instead, and just had one fewer domains registered in the edge router.

It's also possible to add wildcard subdomains. Say we want all subdomains of `example.com` to be handled by WordPress, not just `blog`. Then we'd need only do:

```yaml theme={null}
https://*.{default}/:
  Type: upstream
  Upstream: blog:http

```

Both the edge router and the router container will now get a wildcard configuration and everything will get proxied as you'd expect. The one caveat is that Let's Encrypt doesn't support wildcard certificates just yet, so for the moment you'll need to bring your own wildcard SSL certificate. That's expected to [change in January](https://letsencrypt.org/2017/07/06/wildcard-certificates-coming-jan-2018.html), though, and we'll start supporting automatic Let's Encrypt wildcard certificates as soon as they tell us we can.

### Multiple domains

Now we get to the fun part. What happens if you have multiple apex domains, not just subdomains? Suppose we want to have `exampleblog.com` instead of `blog.example.com`. What then?

Platform.sh allows you to associate any number of domains with a project, but only one gets associated with the special `{default}` placeholder. Fortunately, the others can still be used in `routes.yaml` literally. To wit:

```yaml theme={null}
https://{default}/:
  type: upstream
  upstream: site:http
https://www.{default}/:
  type: redirect
  to: https://{default}/
https://exampleblog.com/:
  Type: upstream
  Upstream: blog:http

```

In production, this works exactly as you'd expect. It does the exact same thing as the `blog.example.com` version, except with a different domain name. It's on a development branch that it becomes interesting. What happens to `exampleblog.com`?

Simple: We toss that domain name into the mix to produce the generated domain name. Strictly speaking we always do, but if the domain is just `{default}` you don't see it. Any static part of the domain in `routes.yaml` is simply prepended to the domain name we generate, so in the "update" branch we end up with `exampleblog.com.update-abc123-cdhuk7d6hhcsg.us.platform.sh`. In fact, our previous `blog.example.com` example would have a dev branch of `blog.update-abc123-cdhuk7d6hhcsg.us.platform.sh`.

(At this point those of you who have been with us for a while are probably wondering where the `---`s went. As of December 2017, we are using periods rather than dashes to separate the generated parts of the domain, which makes the domains more compatible with Let's Encrypt. Existing projects didn't change to avoid breaking any existing links or DNS records you may have. If you want to switch your project to using dots, though, just file a ticket through your project interface and we'll flip the switch for you.)

### Route all the domains!

The final use case to consider is a single container hosting multiple apex domains. A common example of this setup for our clients would be with the Drupal Domain Access module. Domain Access lets a single Drupal site serve multiple domains, with subsets of the same content available depending on which domain is being viewed. That is, one application would serve both (for example) `example.com` and `example.net`. How do we make the router handle that?

It's now super-simple. First you'd configure both domains in your project. It doesn't matter which is the default. Then, you'd put the following in your `routes.yaml`:

```yaml theme={null}
https://{all}/:
  type: upstream
  upstream: app:http
https://www.{all}/:
  type: redirect
  to: https://{all}/

```

The `{all}` placeholder iterates over all configured domains and makes an entry for each. On production, therefore, the above configuration would expand out to:

```yaml theme={null}
https://example.com/:
  type: upstream
  upstream: app:http
https://www.example.com/:
  type: redirect
  to: https://example.com/
https://example.net/:
  type: upstream
  upstream: app:http
https://www.example.net/:
  type: redirect
  to: https://example.net/

```

(Plus an HTTP->HTTPS redirect for each that I've omitted for space.) That will result in four entries (one for each domain) being added to the edge router, all pointing at the router container, and the router container will get 8 entries in its lookup table (the four above plus the HTTPS redirect for each). If we add a third domain, another 2 effective routes will get to the list.

What about development environments? The same pattern applies. The `update` branch would effectively turn into:

* example.com.update-abc123-cdhuk7d6hhcsg.us.platform.sh
* [www.example.com.update-abc123-cdhuk7d6hhcsg.us.platform.sh](http://www.example.com.update-abc123-cdhuk7d6hhcsg.us.platform.sh)
* example.net.update-abc123-cdhuk7d6hhcsg.us.platform.sh
* [www.example.net.update-abc123-cdhuk7d6hhcsg.us.platform.sh](http://www.example.net.update-abc123-cdhuk7d6hhcsg.us.platform.sh)

And then of course you can mix and match all of the above functionality as desired.

### Even more flexibility

Each of those routes, of course, can also have a variety of other configuration on it. Path-specific redirects, caching, [TLS configuration](https://docs.platform.sh/configuration/routes/https.html), and so on can be configured per-route. As mentioned, you can even set routes for specific paths that will have their own configuration; you can use that to set different cache rules for a mostly-static site vs. a blog vs. a Websocket path, for instance. Or create redirects from old, legacy URLs to current ones, right in the router. Or... various other things. Our [documentation](https://docs.platform.sh/configuration/routes.html) has more details on the many other options available.

So that's routing on Platform.sh. Your site is under your control, just as it should be. We just make it all work for you.
