1 | <h1 align="center">Fastify</h1>
|
2 |
|
3 | ## Recommendations
|
4 |
|
5 | This document contains a set recommendations, or best practices, when using
|
6 | Fastify.
|
7 |
|
8 | * [Use A Reverse Proxy](#reverseproxy)
|
9 |
|
10 | ## Use A Reverse Proxy
|
11 | <a id="reverseproxy"></a>
|
12 |
|
13 | Node.js is an early adopter of frameworks shipping with an easy to use web
|
14 | server within the standard library. Previously, with languages like PHP or
|
15 | Python, one would need either a web server with specific support for the
|
16 | language or the ability to setup some sort of [CGI gateway][cgi] that works
|
17 | with the language. With Node.js, one can simply write an application that
|
18 | _directly_ handles HTTP requests. As a result, the temptation is to write
|
19 | applications that handle requests for multiple domains, listen on multiple
|
20 | ports (i.e. HTTP _and_ HTTPS), and various other scenarios and combinations
|
21 | thereof. Further, the temptation is to then expose these applications directly
|
22 | to the Internet to handle requests.
|
23 |
|
24 | The Fastify team **strongly** considers this to be an anti-pattern and extremely
|
25 | bad practice:
|
26 |
|
27 | 1. It adds unnecessary complexity to the application by diluting its focus.
|
28 | 2. It prevents [horizontal scalability][scale-horiz].
|
29 |
|
30 | See [Why should I use a Reverse Proxy if Node.js is Production Ready?][why-use]
|
31 | for a more thorough discussion of why one should opt to use a reverse proxy.
|
32 |
|
33 | For a concrete example, consider the situation where:
|
34 |
|
35 | 1. The app needs multiple instances to handle load.
|
36 | 1. The app needs TLS termination.
|
37 | 1. The app needs to redirect HTTP requests to HTTPS.
|
38 | 1. The app needs to serve multiple domains.
|
39 | 1. The app needs to serve static resources, e.g. jpeg files.
|
40 |
|
41 | There are many reverse proxy solutions available, and your environment may
|
42 | dictate the solution to use, e.g. AWS or GCP. But given the above, we could use
|
43 | [HAProxy][haproxy] to solve these requirements:
|
44 |
|
45 | ```conf
|
46 | # The global section defines base HAProxy (engine) instance configuration.
|
47 | global
|
48 | log /dev/log syslog
|
49 | maxconn 4096
|
50 | chroot /var/lib/haproxy
|
51 | user haproxy
|
52 | group haproxy
|
53 |
|
54 | # Set some baseline TLS options.
|
55 | tune.ssl.default-dh-param 2048
|
56 | ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
|
57 | ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
|
58 | ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11
|
59 | ssl-default-server-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
|
60 |
|
61 | # Each defaults section defines options that will apply to each subsequent
|
62 | # subsection until another defaults section is encountered.
|
63 | defaults
|
64 | log global
|
65 | mode http
|
66 | option httplog
|
67 | option dontlognull
|
68 | retries 3
|
69 | option redispatch
|
70 | # The following option make haproxy close connections to backend servers
|
71 | # instead of keeping them open. This can alleviate unexpected connection
|
72 | # reset errors in the Node process.
|
73 | option http-server-close
|
74 | maxconn 2000
|
75 | timeout connect 5000
|
76 | timeout client 50000
|
77 | timeout server 50000
|
78 |
|
79 | # Enable content compression for specific content types.
|
80 | compression algo gzip
|
81 | compression type text/html text/plain text/css application/javascript
|
82 |
|
83 | # A "frontend" section defines a public listener, i.e. an "http server"
|
84 | # as far as clients are concerned.
|
85 | frontend proxy
|
86 | # The IP address here would be the _public_ IP address of the server.
|
87 | # Here, we use a private address as an example.
|
88 | bind 10.0.0.10:80
|
89 | # This redirect rule will redirect all traffic that is not TLS traffic
|
90 | # to the same incoming request URL on the HTTPS port.
|
91 | redirect scheme https code 308 if !{ ssl_fc }
|
92 | # Technically this use_backend directive is useless since we are simply
|
93 | # redirecting all traffic to this frontend to the HTTPS frontend. It is
|
94 | # merely included here for completeness sake.
|
95 | use_backend default-server
|
96 |
|
97 | # This frontend defines our primary, TLS only, listener. It is here where
|
98 | # we will define the TLS certificates to expose and how to direct incoming
|
99 | # requests.
|
100 | frontend proxy-ssl
|
101 | # The `/etc/haproxy/certs` directory in this example contains a set of
|
102 | # certificate PEM files that are named for the domains the certificates are
|
103 | # issued for. When HAProxy starts, it will read this directory, load all of
|
104 | # the certificates it finds here, and use SNI matching to apply the correct
|
105 | # certificate to the connection.
|
106 | bind 10.0.0.10:443 ssl crt /etc/haproxy/certs
|
107 |
|
108 | # Here we define rule pairs to handle static resources. Any incoming request
|
109 | # that has a path starting with `/static`, e.g.
|
110 | # `https://one.example.com/static/foo.jpeg`, will be redirected to the
|
111 | # static resources server.
|
112 | acl is_static path -i -m beg /static
|
113 | use_backend static-backend if is_static
|
114 |
|
115 | # Here we define rule pairs to direct requests to appropriate Node.js
|
116 | # servers based on the requested domain. The `acl` line is used to match
|
117 | # the incoming hostname and define a boolean indicating if it is a match.
|
118 | # The `use_backend` line is used to direct the traffic if the boolean is
|
119 | # true.
|
120 | acl example1 hdr_sub(Host) one.example.com
|
121 | use_backend example1-backend if example1
|
122 |
|
123 | acl example2 hdr_sub(Host) two.example.com
|
124 | use_backend example2-backend if example2
|
125 |
|
126 | # Finally, we have a fallback redirect if none of the requested hosts
|
127 | # match the above rules.
|
128 | default_backend default-server
|
129 |
|
130 | # A "backend" is used to tell HAProxy where to request information for the
|
131 | # proxied request. These sections are where we will define where our Node.js
|
132 | # apps live and any other servers for things like static assets.
|
133 | backend default-server
|
134 | # In this example we are defaulting unmatched domain requests to a single
|
135 | # backend server for all requests. Notice that the backend server does not
|
136 | # have to be serving TLS requests. This is called "TLS termination": the TLS
|
137 | # connection is "terminated" at the reverse proxy.
|
138 | # It is possible to also proxy to backend servers that are themselves serving
|
139 | # requests over TLS, but that is outside the scope of this example.
|
140 | server server1 10.10.10.2:80
|
141 |
|
142 | # This backend configuration will serve requests for `https://one.example.com`
|
143 | # by proxying requests to three backend servers in a round-robin manner.
|
144 | backend example1-backend
|
145 | server example1-1 10.10.11.2:80
|
146 | server example1-2 10.10.11.2:80
|
147 | server example2-2 10.10.11.3:80
|
148 |
|
149 | # This one serves requests for `https://two.example.com`
|
150 | backend example2-backend
|
151 | server example2-1 10.10.12.2:80
|
152 | server example2-2 10.10.12.2:80
|
153 | server example2-3 10.10.12.3:80
|
154 |
|
155 | # This backend handles the static resources requests.
|
156 | backend static-backend
|
157 | server static-server1 10.10.9.2:80
|
158 | ```
|
159 |
|
160 | [cgi]: https://en.wikipedia.org/wiki/Common_Gateway_Interface
|
161 | [scale-horiz]: https://en.wikipedia.org/wiki/Scalability#Horizontal
|
162 | [why-use]: https://web.archive.org/web/20190821102906/https://medium.com/intrinsic/why-should-i-use-a-reverse-proxy-if-node-js-is-production-ready-5a079408b2ca
|
163 | [haproxy]: https://www.haproxy.org/
|