{"id":23419709,"url":"https://github.com/pdroll/raspberry-pi-node-server","last_synced_at":"2025-09-19T01:42:17.663Z","repository":{"id":76116543,"uuid":"95837148","full_name":"pdroll/Raspberry-Pi-Node-Server","owner":"pdroll","description":"Complete instructions to set up a Raspberry Pi as a production quality Node.js and MongoDB server.","archived":false,"fork":false,"pushed_at":"2017-09-27T02:55:24.000Z","size":12,"stargazers_count":19,"open_issues_count":2,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-11T05:35:12.762Z","etag":null,"topics":["mongodb","nginx","nginx-proxy","nodejs","raspberry-pi","raspbian"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pdroll.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"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":"2017-06-30T01:55:15.000Z","updated_at":"2025-01-31T14:10:21.000Z","dependencies_parsed_at":"2023-07-03T05:47:01.556Z","dependency_job_id":null,"html_url":"https://github.com/pdroll/Raspberry-Pi-Node-Server","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pdroll/Raspberry-Pi-Node-Server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdroll%2FRaspberry-Pi-Node-Server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdroll%2FRaspberry-Pi-Node-Server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdroll%2FRaspberry-Pi-Node-Server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdroll%2FRaspberry-Pi-Node-Server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pdroll","download_url":"https://codeload.github.com/pdroll/Raspberry-Pi-Node-Server/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdroll%2FRaspberry-Pi-Node-Server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275867350,"owners_count":25542801,"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","status":"online","status_checked_at":"2025-09-18T02:00:09.552Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["mongodb","nginx","nginx-proxy","nodejs","raspberry-pi","raspbian"],"created_at":"2024-12-23T01:28:05.143Z","updated_at":"2025-09-19T01:42:17.617Z","avatar_url":"https://github.com/pdroll.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Raspberry Pi Node Server - The complete instructions\n\nSo you just bought a new Raspberry Pi and you want to set it up as a web server? And you want to use Node.js? And have access to MongoDB?\n\nCongrats, you're in the right place. You're only 10 steps away from Raspberry Pi-Node-Mongo goodness, and three of those steps are optional. Let's get started.\n\n## Steps\n1. [Install Raspbian onto SD Card](#step-1)\n2. [Boot from USB (optional)](#step-2)\n3. [Initial Configuration](#step-3)\n4. [Internal Networking](#step-4)\n5. [Install all the things](#step-5)\n6. [Get your code, Run your code](#step-6)\n7. [Proxy requests with nginx](#step-7)\n8. [Make your web server available from ... the web.](#step-8)\n9. [Enable SSL like a boss (optional)](#step-9)\n10. [Enable Cross Origin Requests (optional)](#step-10)\n11. [Step 11: BONUS! Control GPIO pins with Node.js](#step-11)\n\n## \u003ca name=\"step-1\"\u003e\u003c/a\u003e Step 1: Install Raspbian onto SD Card\nOn your development machine, [Download Raspbian Lite](https://www.raspberrypi.org/downloads/raspbian/). Unzip that file to get the `.img`.\n\nIf your dev machine is Windows or Linux, you can lookup [directions online](https://www.raspberrypi.org/documentation/installation/installing-images/README.md).\n\nIf you are using a Mac:\nFormat SD card, MS-DOS (FAT) using Disk Utility. Run `diskutil list` from terminal to get number of SD Card disk.\n\nThen to copy the OS to the SD card, run (changing the location and filename of the .img file you just downloaded):\n```\nsudo dd bs=1m if=~/Downloads/2017-03-02-raspbian-jessie-lite.img of=/dev/rdisk{NUMBER_OF_SD_CARD_DISK}\n```\nAfter a while, you should see output similar to\n\n```\n1329+0 records in\n1329+0 records out\n1393557504 bytes transferred in 367.987715 secs (3786967 bytes/sec)\n```\n\nOnce done, eject the SD card by running.\n```\nsudo diskutil eject /dev/disk{NUMBER_OF_SD_CARD_DISK}\n```\n\nNow plug that SD card, a montior and a keyboard into your Raspberry Pi, power it up and dive in.\n\n*Note* - By default Raspbian ships with the British keyboard layout, which makes some keys hard to find. If you can't find the `|` character, ensure Num Lock is on, hold alt, then press 1,2,4 on the keypad.\n\n## \u003ca name=\"step-2\"\u003e\u003c/a\u003eStep 2:  Boot from USB (optional)\nIf your paranoid about the write limits of SD cards over time, or have been burned by a corupted SD card one too many times, you can instruct your Pi to boot from a USB flash drive instead. If you're not worried about this, skip ahead to [Step 3](#step-3).\n\nIf you have a Raspberry Pi 3, there are [new instructions](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bootmodes/msd.md) on how to accomplish this. The cool thing about this, is that after following those steps, you can remove the SD and boot entirely from USB. Pretty cool.\n\nLogin using username `pi` and password `rapsberry`, then:\n```\nsudo apt-get update \u0026\u0026 sudo apt-get upgrade\necho program_usb_boot_mode=1 | sudo tee -a /boot/config.txt\nsudo reboot\n```\nLog back in as `pi`, and run the following command:\n\n```\nvcgencmd otp_dump | grep 17:\n```\n\nIf that outputs `17:3020000a`, congrats, you win.\n\nNow you need to add Raspbian to your USB drive. On your dev machine, repeat [Step 1](#step-1), only with the USB drive, not the SD card. Once that finishes up, shutdown the Raspberry Pi (`sudo shutdown -h now`), eject the SD card, insert your USB drive and plug her in.\n\nIf all went as planned your Pi should startup as normal after 5-10 seconds and you're now done with the SD card for good!\n\nIf you have a Pi older than 3, you can still run your server off of a USB stick, but you'll still need the SD card for booting up. Instructions for that can be [found here](http://jonathanmh.com/boot-raspberry-pi-from-a-usb-stick/).\n\n## \u003ca name=\"step-3\"\u003e\u003c/a\u003e Step 3) Initial Configuration\nUsing `sudo raspi-config`, change hostname, enable SSH, wait for network on boot, reduce GPU to 16MB.\n\nOptionally, you can edit the message that appears in the terminal when you first log in. I like to add some [ascii art](http://patorjk.com/software/taag/) to match the Pi's host name. To do so, run:\n\n```\nsudo nano /etc/motd\n```\n\nand paste in the message you want displayed.\n\n### Setup new user\nIt's generally a good idea to have a different linux user for each app running on your server. To create a new user, for an `api` app for example:\n```\nsudo useradd -m api -G sudo\nsudo passwd api\nlogout\n```\nLogin with the user and password you just created. After you've confirmed your new user has sudo privelages, you can delete the default `pi` user for good measure:\n```\nsudo deluser pi\n```\n\n## \u003ca name=\"step-4\"\u003e\u003c/a\u003e Step 4) Internal Networking\nYou can plug your Pi via ethernet cable to your router or setup Wifi.\n\nTo connect to Wifi: `sudo nano /etc/wpa_supplicant/wpa_supplicant.conf`. Add the following:\n```\nnetwork={\nssid=\"SSID\"\npsk=\"WIFI PASSWORD\"\n}\n```\n\nMost wireless routers these days are also DHCP servers, which means by default, you will get a different IP address every time you connect to the network. This isn't ideal, as you'll need to know where to find your server on the network if you're going to route requests to or SSH into it.\n\nTo assign Static IP address to your Pi on the internal network:\n\n```\nsudo nano /etc/dhcpcd.conf\n```\n Add the following:\n```\ninterface wlan0\n\n# Desired IP Address. Keep /24\nstatic ip_address=192.168.0.200/24\n# IP Address of router\nstatic routers=192.168.0.1\n# IP Address of router\nstatic domain_name_servers=192.168.0.1\n```\n\nRestart Wifi:\n```\nsudo ifdown wlan0\nsudo ifup wlan0\n```\n\nIf that is done correctly, you can now connect to your Pi via SSH on the local network. What that means is you can ditch the keyboard and monitor that are plugged into your Pi, and do the rest of the work from your normal dev machine. Woo!\n\nFrom the terminal on your dev machine (or whatever SSH client you use on Windows), you can run\n\n```\nssh api@192.168.0.200\n```\nUse the password you set when you created the new user.\n\n## \u003ca name=\"step-5\"\u003e\u003c/a\u003e Step 5) Install all the things:\n\nPull in reference to Node v6 (latest LTS version). Change version digit in the URL if you want a different version (https://deb.nodesource.com/setup_7.x for example)\n```\ncurl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -\n```\n\nInstall the things\n```\nsudo apt-get update\nsudo apt-get install git nodejs nginx -y\n```\nInstall will take a decent amount of time to run (10-20 minutes)\n\nInstall yarn and pm2 with npm\n```\nsudo npm install -g yarn pm2\n```\n\n### Configure PM2 Logrotate (optional)\nPM2, the process manager that keeps your Node scripts running, outputs great log files. However, without configuration these log files can eat up more and more storage on you Pi. The `pm2-logrotate` module can help with this issue.\n\n```\n# Install the module\npm2 install pm2-logrotate\n# Keep at most 90 days of logs\npm2 set pm2-logrotate:retain 90\n# Gzip old log files to save space\npm2 set pm2-logrotate:compress true\n```\n\n### Upgrade Nginx (optional)\nInstalling Nginx in the normal way like we just did will install a much older version of the software, something like 1.6. If you are going want to do modern things, including taking advantage of the HTTP/2 protocol, you're going to need a newer version of Nginx (at least 1.9.5). If you're fine with the older version, feel free to skip this section.\n\nAfter installing nginx in the normal way above, remove it with `apt-get`:\n\n```\nsudo apt-get remove nginx\n```\n\nWe are going to download and compile the newest version of Nginx, along with OpenSSL and PCRE. Luckily, a guy by the name of [Matt Wilcox]() has written [a script to do exactly that on a Raspi](). Download that script via wget:\n\n```\ncd ~\nwget https://gist.githubusercontent.com/MattWilcox/402e2e8aa2e1c132ee24/raw/1a4f1f78cb69b715d7e63a6c9b97032bc9ddebd8/build_nginx.sh\n```\n\nCheck for the latest version numbers of [OpenSSL](https://www.openssl.org/source/) (version 1.0.2d), [PCRE](http://www.pcre.org/) (version 8.39) and [Nginx](http://nginx.org/en/download.html) (version 1.9.7). The latest versions I could get to work are in parenthesis.\n\nEdit variables at the top of the build script we just downloaded to reflect these version numbers.\n\n```\nsudo nano ~/build_nginx.sh\n```\n\nMake the file executable and run it:\n\n```\nsudo chmod +x build_nginx.sh\nsudo ./build_nginx.sh\n```\n\nThis will take a while to run. Be chill.\n\n### Tweak Nginx (optional)\nBy default Nginx will advertise it's exact version number, in the `server` header and a couple other places. You can stop this behavior by editing the nginx config file:\n\n```\nsudo nano /etc/nginx/nginx.conf\n```\nand ensuring the following line is NOT commented out:\n```\nserver_tokens off;\n```\n\nNginx also ships with a default site that just shows a \"Welcome to nginx on Debian!\" page. This isn't really useful to us, and since we'll be adding new nginx sites, we can disable this default site. To do so, run:\n\n```\nsudo nano /etc/nginx/sites-available/default\n```\nand add `return 404;` in the line right after the listen directives.\n\nReload nginx to see our changes:\n```\n# Test your changes\nsudo nginx -t\n# If that says OK, reload\nsudo service nginx restart\n```\n\n### MongoDB\nIf you want mongodb v2.4, just run:\n\n```\nsudo apt-get install mongodb\n```\n\nIf you want v3.0.14 (which is the highest version of Mongo that supports a 32bit system), [do the following](http://andyfelong.com/2017/03/mongodb-3-0-14-binaries-for-raspberry-pi-3/):\n\nCheck for mongodb user\n```\ngrep mongodb /etc/passwd\n# if NO mongodb user, create:\nsudo adduser --ingroup nogroup --shell /etc/false --disabled-password --gecos \"\" \\\n--no-create-home mongodb\n```\n\nDownload Binaries\n```\nsudo apt-get upgrade\nmkdir ~/mongo\n# Core Mongo\nwget -P ~/mongo/ http://andyfelong.com/downloads/core_mongodb_3_0_14.tar.gz\n# Mongo Tools\nwget -P ~/mongo/ http://andyfelong.com/downloads/tools_mongodb_3_0_14.tar.gz\n\n# Unzip Core files:\ntar -xvzf core_mongodb_3_0_14.tar.gz\n\n# Move the files where they need to go\nsudo chown root:root mongo*\nsudo chmod 755 mongo*\nsudo strip mongo*\nsudo cp -p mongo* /usr/bin\n\n# Log and config directories\nsudo mkdir /var/log/mongodb\nsudo chown mongodb:nogroup /var/log/mongodb\nsudo mkdir /var/lib/mongodb\nsudo chown mongodb:root /var/lib/mongodb\nsudo chmod 775 /var/lib/mongodb\n\ncd /etc\nsudo nano mongodb.conf\n```\nAdd the following text to mongdb.conf:\n\n```\nbind_ip = 127.0.0.1\nquiet = true\ndbpath = /var/lib/mongodb\nlogpath = /var/log/mongodb/mongod.log\nlogappend = true\nstorageEngine = mmapv1\n```\n__The wiredTiger storage engine isn't supported on the 32-bit ARM chip. :'(__\n\nAdd to `service`:\n\n```\ncd /lib/systemd/system\nsudo nano mongodb.service\n```\n\nAdd the following text to `mongodb.service`:\n```\n[Unit]\nDescription=High-performance, schema-free document-oriented database\nAfter=network.target\n\n[Service]\nUser=mongodb\nExecStart=/usr/bin/mongod --quiet --config /etc/mongodb.conf\n\n[Install]\nWantedBy=multi-user.target\n```\n\nAdd the following line to `~/.bashrc` (it's a setting that mongo needs to run):\n\n```\nexport LC_ALL=C\n```\n\nStart up mongo!\n```\nsudo systemctl unmask mongodb\nsudo service mongodb start\n```\n\nEnable all the mongo tools:\n\n```\ncd ~/mongo\nmkdir tools\ntar -xzvf tools_mongodb_3_0_14.tar.gz --directory tools/\ncd tools\nsudo strip ./*\nsudo chown root:root ./*\nsudo chmod 755 ./*\nsudo mv ./* /usr/bin\n```\nMongo 3 on your Raspi 3. Boomshakalaka.\n\n## \u003ca name=\"step-6\"\u003e\u003c/a\u003e Step 6) Get your code, Run your code\nGenerate SSH Key\n```\nssh-keygen -f ~/.ssh/id_rsa -C \"id_rsa\"\n```\nIn a browser, go to Github or Bitbucket and Add SSH Key OR Deploy/Access Key in your repository. Paste in output from:\n```\ncat ~/.ssh/id_rsa.pub\n```\n\nClone your repository into your home dir.\n\n```\ncd ~ \u0026\u0026 git clone git@URL_OF_REPOSITORY.git\n```\n\nIf needed, add app config env vars to .bashrc\n```\nsudo nano ~/.bashrc\n# export APP_CONFIG_OPTION=foobar\n```\n\nRun your code with PM2:\n```\npm2 start ~/APP_DIRECTORY/index.js\n```\nMake sure your app starts up when raspi restarts:\n\n```\npm2 startup\n## This command will tell you to run another command as sudo. Do that.\npm2 save\n```\n\nIf you run into issues with your PM2 not starting on reboot, even after running the above commands, you can add the following cronjob: `crontab -e`\n\n```\n@reboot cd ~/APP_DIRECTORY/ \u0026\u0026 pm2 start index.js\n```\n\nYour app should now be available to any computer on your local network. You can visit\n\n[192.168.0.200:3000](http://192.168.0.200:3000)\n\nand you'll see your app running (where the IP is the static internal IP address you assinged the PI, and the port is port your Node.js code is litening on).\n\n## \u003ca name=\"step-7\"\u003e\u003c/a\u003e Step 7) Proxy requests with nginx\nYou're node app most likely isn't running on port 80. It turns out it's kind of a pain to get node servers to directly listen on port 80. Enter nginx.\n\nAdd new nginx site block:\n```\nsudo nano /etc/nginx/sites-available/{APP_NAME}\nsudo service nginx reload\n```\n\nAdd the following text:\n\n```\nupstream {APP_NAME} {\n    server 127.0.0.1:{NODE_APP_PORT};\n    keepalive 64;\n}\n\nserver {\n    listen 80;\n\n    server_name {DESIRED_DOMAIN_NAME};\n\n    location / {\n        proxy_pass http://{APP_NAME}/;\n        proxy_http_version 1.1;\n        proxy_cache_bypass $http_upgrade;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection 'upgrade';\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_max_temp_file_size 0;\n        proxy_redirect off;\n        proxy_read_timeout 240s;\n    }\n}\n```\n\nEnable newly configured site:\n\n```\nsudo ln -s /etc/nginx/sites-available/{APP_NAME} /etc/nginx/sites-enabled/\nsudo service nginx reload\n```\n\nYou can edit the hosts file (/etc/hosts) on your dev machine to point your newly added domain name to your Raspberry Pi's static internal IP to test your nginx config.\n\n## \u003ca name=\"step-8\"\u003e\u003c/a\u003e Step 8) Make your web server available from ... the web.\n\nIf you don't have a static IP from your internet provider (not sure? Odds are you don't), you'll need to use a Dynamic DNS service.\n\nHead over to [Duck DNS](http://www.duckdns.org/). It's free and awesome. Make an account and claim a domain. Follow instructions on [this page](https://www.duckdns.org/install.jsp?tab=linux-cron).\n\n```\nmkdir ~/duckdns\ncd ~/duckdns\nnano duck.sh\n```\n\nAdd the following line to `duck.sh`\n\n```\necho url=\"https://www.duckdns.org/update?domains={YOUR_DOMAIN}\u0026token={YOUR_TOKEN}4\u0026ip=\" | curl -k -o ~/duckdns/duck.log -K -\n```\nMake the file executable:\n```\nchmod 700 duck.sh\n```\n\nRun that file every 5 minutes. Edit your crontab (`crontab -e`) and add the following line:\n```\n*/5 * * * * ~/duckdns/duck.sh \u003e/dev/null 2\u003e\u00261\n```\n\nTest the script:\n```\n./duck.sh\ncat duck.log\n```\n\nIf it's says `OK`, we good.\n\n### Forward incoming requests to your network\n You'll need to hop into your router's admin screen and forward incoming requests on specific ports to your raspi. [PortForward.com/](https://portforward.com/) has more info on specifics on how to do this on various routers.\n\nForward only ports 80 (HTTP), 443 (HTTPS), and 22 (SSH) to the static internal IP you assigned your Pi.\n\nOnce that's done correctly, you should be able to go to [{APP_NAME}.duckdns.org](http://{APP_NAME}.duckdns.org) and see your app. Woo.\n\n\n#### Use your own domain\nOwn a domain name? If you've spent this much time hacking on a raspberry pi, I'm going to bet you do.\n\nYou can add a CNAME record to access your site via a submain of the domain name you own.\nThe CNAME record should point to your {APP_NAME}.duckdns.org domain and the host should be your desired subdomain.\n\nOnce that change is saved and propogates, you should be able to access your Node.js app via a domain like:\n\n[node-is-cool.myawesomedomain.com](http://node-is-cool.myawesomedomain.com)\n\n### \u003ca name=\"step-9\"\u003e\u003c/a\u003e Step 9) Enable SSL like a boss (optional)\nLet's Encrypt makes enabling https free and relatively easy. You're already this deep in. Why stop now? [Link](https://www.linuxbabe.com/linux-server/install-lets-encrypt-free-tlsssl-certificate-nginx-debian-8-server)\n\n```\nsudo nano /etc/apt/sources.list\n```\nand add the following line to the end:\n\n```\ndeb http://ftp.debian.org/debian jessie-backports main\n```\n\nInstall the client:\n```\nsudo apt-get update\nsudo apt-get install letsencrypt -t jessie-backports\n```\n\nGet a cert:\n\n```\nsudo service nginx stop\nsudo letsencrypt certonly --email \u003cyour-email-address\u003e -d \u003cyour-domain-name\u003e\n```\n\nSelect option 2, standalone.\n\nRenew the cert automatically by running:\n\n```\nmkdir ~/log\nsudo crontab -e\n```\n\nand pasting:\n\n```\n# Check for cert renewal every Monday at 2:30am\n30 2 * * 1 /usr/bin/certbot renew \u003e\u003e /home/{username}/log/le-renew.log\n# Reload Nginx every Monday at 2:35am, to use potential new cert\n35 2 * * 1 /etc/init.d/nginx reload\n```\n\nNow we need to configure nginx to serve your app over SSL.\n\nGenerate strong Diffie–Hellman key (this will take a while to run):\n\n```\nsudo mkdir /etc/nginx/ssl/\nsudo openssl dhparam -out /etc/ssl/certs/dhparam2048.pem 2048\n```\n\nAdd SSL params config file:\n\n```\nsudo nano /etc/nginx/snippets/ssl-params.conf\n```\n\nAnd paste in:\n\n```\nssl_protocols TLSv1.2;\nssl_prefer_server_ciphers on;\nssl_ciphers \"EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH\";\nssl_ecdh_curve secp384r1;\nssl_dhparam /etc/ssl/certs/dhparam2048.pem;\nssl_session_cache shared:SSL:10m;\nssl_session_tickets off;\nssl_stapling on;\nssl_stapling_verify on;\nresolver 8.8.8.8 8.8.4.4 valid=300s;\nresolver_timeout 5s;\nadd_header Strict-Transport-Security \"max-age=31536000; includeSubDomains;\" always;\nadd_header X-Frame-Options \"SAMEORIGIN\";\nadd_header X-Content-Type-Options nosniff;\nadd_header Referrer-Policy \"no-referrer\";\nadd_header X-XSS-Protection \"1; mode=block\";\n```\n\nNote: If you did not upgrade Nginx, remove the word `always` from the `Strict-Transport-Security` line, so it reads:\n\n```\nadd_header Strict-Transport-Security \"max-age=31536000; includeSubDomains;\";\n```\n\nSee [cipherli.st](https://cipherli.st/) for explanations for all of these SSL settings.\n\nEdit your nginx site config file:\n\n```\nsudo nano /etc/nginx/site-available/{APP_NAME}\n```\n\n\nDelete everything and paste in the following:\n```\nupstream {APP_NAME} {\n    server 127.0.0.1:{NODE_APP_PORT};\n    keepalive 64;\n}\n\nserver {\n    listen 80;\n    server_name {DOMAIN_NAME};\n    return 301 https://$host$request_uri;\n}\n\nserver {\n\n    listen 443 ssl http2;\n\n    server_name {DOMAIN_NAME};\n\n    ssl_certificate /etc/letsencrypt/live/{DOMAIN_NAME}/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/{DOMAIN_NAME}/privkey.pem;\n\n    include snippets/ssl-params.conf;\n\n    location / {\n        proxy_pass http://{APP_NAME}/;\n        proxy_http_version 1.1;\n        proxy_cache_bypass $http_upgrade;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection 'upgrade';\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_max_temp_file_size 0;\n        proxy_redirect off;\n        proxy_read_timeout 240s;\n    }\n}\n```\n\nNote: If you did not upgrade Nginx, remove `http2` from the `listen 443` line so it reads:\n```\nlisten 443 ssl;\n```\n\nTest your nginx config:\n\n```\nsudo nginx -t\n```\n\nIf all good, reload nginx:\n\n```\nsudo service nginx reload\n```\n\n## \u003ca name=\"step-10\"\u003e\u003c/a\u003e Step 10: Enable Cross Origin Requests (optional)\n\nIf you're app is an API that will be called via ajax on other domains, we'll need to adjust our nginx site config to tell browsers that we know what we're doing.\n\n```\nsudo nano /etc/nginx/site-available/{APP_NAME}\n```\nDelete everything and paste in the following:\n\n```\nupstream {APP_NAME} {\n    server 127.0.0.1:{NODE_APP_PORT};\n    keepalive 64;\n}\n\nserver {\n    listen 80;\n    server_name {DOMAIN_NAME};\n    return 301 https://$host$request_uri;\n}\n\nserver {\n\n    listen 443 ssl http2;\n\n    server_name {DOMAIN_NAME};\n\n    ssl_certificate /etc/letsencrypt/live/{DOMAIN_NAME}/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/{DOMAIN_NAME}/privkey.pem;\n\n    include snippets/ssl-params.conf;\n\n    location / {\n        proxy_pass http://{APP_NAME}/;\n        proxy_http_version 1.1;\n        proxy_cache_bypass $http_upgrade;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection 'upgrade';\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\tproxy_set_header X-Forwarded-Proto $scheme;\n        proxy_max_temp_file_size 0;\n        proxy_redirect off;\n        proxy_read_timeout 240s;\n\n        if ($http_origin ~* (^https?://.*\\.example\\.com$|^https?://localhost:\\d+$)) {\n            set $cors \"true\";\n        }\n\n        # See https://gist.github.com/algal/5480916\n        # for CORS setup\n        # OPTIONS indicates a CORS pre-flight request\n        if ($request_method = 'OPTIONS') {\n            set $cors \"${cors}options\";\n        }\n\n        # non-OPTIONS indicates a normal CORS request\n        if ($request_method = 'GET') {\n            set $cors \"${cors}normal\";\n        }\n        if ($request_method = 'PUT') {\n            set $cors \"${cors}normal\";\n        }\n        if ($request_method = 'PATCH') {\n            set $cors \"${cors}normal\";\n        }\n        if ($request_method = 'DELETE') {\n            set $cors \"${cors}normal\";\n        }\n        if ($request_method = 'POST') {\n            set $cors \"${cors}normal\";\n        }\n\n        # if it's a normal request, set the standard CORS responses header\n        if ($cors = \"truenormal\") {\n            add_header 'Access-Control-Allow-Origin' \"$http_origin\" always;\n            add_header 'Access-Control-Allow-Credentials' 'true' always;\n            add_header 'Access-Control-Expose-Headers' 'accesstoken, refreshtoken' always;\n        }\n\n        if ($cors = \"trueoptions\") {\n            add_header 'Access-Control-Allow-Origin' \"$http_origin\";\n            add_header 'Access-Control-Allow-Credentials' 'true';\n\n            add_header 'Access-Control-Max-Age' 1728000;\n            add_header 'Access-Control-Allow-Methods' 'GET, PUT, PATCH, DELETE, POST, OPTIONS';\n            add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since';\n\n            add_header 'Content-Length' 0;\n            add_header 'Content-Type' 'text/plain charset=UTF-8';\n            return 204;\n        }\n    }\n}\n```\n\nChange the following line:\n```\nif ($http_origin ~* (^https?://.*\\.example\\.com$|^https?://localhost:\\d+$)) {\n```\n\nto use a regex for whatever domains you want to whitelist. In this example, we're allowing requests from any subdomain on `example.com` and requests from `localhost` on any port number, both on either `http` or `https`.\n\nAlso change this line:\n\n```\n'Access-Control-Expose-Headers' 'accesstoken, refreshtoken';\n```\n\nto list whatever response headers your app is setting which will be needed by the javascript apps calling your API.\n\nCheckout this [annotated config file](https://gist.github.com/algal/5480916) to learn more about each of the additions we're making.\n\n## \u003ca name=\"step-11\"\u003e\u003c/a\u003e Step 11: BONUS! Control GPIO pins with Node.js\nOne of the best things about using a Raspberry Pi is the ability to interact with the physical world using the [General Purpose Input/Output pins](https://www.raspberrypi.org/documentation/usage/gpio-plus-and-raspi2/). The RasPi 3 has 40 pins, 28 of which are programmable. You can wire these into buttons, switchs, relays, servos, sensors, etc to do any sort of cool thing you can dream up. [pinout.xyz/](https://pinout.xyz/) is a great resource to find out which pins do what.\n\nThere exist lots of Node.js libraries to help interact with these pins, but few are kept up to date and work with the latest RasPi models and newest versions of Node. However I found the [RPIO](https://www.npmjs.com/package/rpio) package library and it is fantastic. It has an easy-to-understand interface, is super performant, and supports pretty much all RasPi models and most Node.js versions, including 6.x and 7.x.\n\nTo use it in your Node.js app run:\n\n```\nyarn add rpio\n```\n\nBefore you can you use the library in your Node.js app, you'll need to make sure the user running the Node script has the correct permissions.\n\nAdd the your user to the `gpio` group:\n\n```\nsudo usermod -a -G gpio USERNAME\n```\n\nAnd add a file to tell the interface to trust the `gpio` group:\n\n```\nsudo nano /etc/udev/rules.d/20-gpiomem.rules\n```\n\nand paste in:\n\n```\nSUBSYSTEM==\"bcm2835-gpiomem\", KERNEL==\"gpiomem\", GROUP=\"gpio\", MODE=\"0660\"\n```\n\nAfter restarting the Pi, you are ready to start controlling the pins with your app.\n\nInclude the library in your javascript file:\n\n```\nconst rpio = require('rpio');\n```\n\nSetting a pin as output and toggling it to high is as easy:\n\n```\nrpio.open(12, rpio.OUTPUT, rpio.LOW);\nrpio.write(12, rpio.HIGH);\n```\n\nAnd reading the state of an input pin is just as easy:\n\n```\nrpio.open(15, rpio.INPUT);\nconst state = rpio.read(15);\n```\n\n## You did it!\n\nCongrats!\n\nIf you followed all the steps, you now have a Node.js app mangaged with PM2 with access to locally running MongoDB, being served by an SSL, HTTP/2 Nginx proxy server, that's available to the entire Internet while being hosted on a Raspberry Pi at your house.\n\nWhat else could you want in life besides exactly that?\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpdroll%2Fraspberry-pi-node-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpdroll%2Fraspberry-pi-node-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpdroll%2Fraspberry-pi-node-server/lists"}