1. I chose Digital Ocean for this because I found it as one of the cheapest, reliable and easy to implement infra that I can use for development purposes. So, I assume You Already have an account. So the steps involved are

    Create The SSH key pair Or Use the Existing One

    1. In your local machine terminal run
    cd ~/.ssh

    If you already have SSH keys set up, you should see a file called id_rsa.pub. Copy that file content by,

    pbcopy < ~/.ssh/id_rsa.pub

    If you want to create a new key type, 

    ssh-keygen
    
    Generating public/private rsa key pair.
    
    Enter file in which to save the key (/home/username/.ssh/id_rsa):

    If you choose to overwrite the key, you will not be able to authenticate to the previous infrastructure or resources where you used this key to access. 

    So, it is better to use another name if you want to use a new key[1]. 

    As you are already in the  ~/.sshFolder, the key will be created in that folder. 

    Your identification has been saved in /home/username/.ssh/id_rsa.
    
    Your public key has been saved in /home/username/.ssh/id_rsa.pub.
    
    …… 

     

    Add The Key Pair In Account To Make It Available In the New Machine We Will Create In The Next Step

    1. From the Account section in the navigation menu, select Security. In the SSH keys section, click Add SSH Key.[2]
    2. Next, copy your public key you created in the last step, which should end in .pub into your clipboard and paste the contents into the SSH key content field.
    3. Enter a name for the key. I name this generally as “common-key” as I would attach it to any new machine I would spin up. This key will be attached to the root user which we would diable in the future steps. You can also name the key differently. This is often the name of the machine you copied the public key from.

    Create The Development Machine

    1. This step is pretty easy and self explanatory. Just choose the lowest price, latest version Ubuntu or Ubuntu machine as per your requirement. 
    2. In the time of creation you HAVE TO either select “SSH keys” or “One-time password”. Or you can not access the machine for the first time. 
    3. Select the “SSH keys” option and check the SSH key you have created and saved in the account at step 1. 
    4. You can also add a new SSH key by clicking on “New SSH key” and paste the content of the newly created .pub key described in “Create The SSH key pair” or paste the content of id_rsa.pub (by pbcopy < ~/.ssh/id_rsa.pub command) which is also described at Step 1.    


    Log In To The Development Machine

    1. In ~/.ssh/ Folder, in the config file add at the end of the file, 

    Host hostname-you-like

    User root

    Hostname host-ip-address

    IdentityFile ~/.ssh/common-key-droplets


    As my SSH key file name is common-key-droplets. And it is attached to the root user inside the droplet. Do not forget to change the IP and hostname. You need not put this in the config file if you use copy the default id_rsa.pub file in the time of attaching the SSH key described in “Create The SSH key pair”.  

    1. Now run 

    ssh root@hostname-you-like(setup in the config file)


    [If you tried it before and you got a public key denied message that time and even after correcting the key file and host configure you are still getting this error, you can remove the line related the droplet ip address you set up in your known_hosts file inside /.ssh/ folder.]


    Create A Non Root User 

    1. First, we’re going to add a new user with sudo privileges[3]. Run,

    adduser appuser

    This command prompts for password. Create the password. 

    Afterward, we can see that our user has been created by running id <your_username>, which should output something like the following if we run 

    id appuser

    uid=1000(appuser) gid=1000(appuser) groups=1000(appuser)

    1. In order to run some of the commands on the server, such as restarting services, we need to add our new user to the sudo group. Do this by running the following command:

    usermod -aG sudo appuser

    1. Next, we need to add our SSH key to the new user. This allows us to log in without a password, which is important because we’re planning to disable password logins for this server. So run,

    su – appuser

    mkdir ~/.ssh

    chmod 700 ~/.ssh

    nano ~/.ssh/authorized_keys

    1. Now copy paste the content of the newly created .pub key described in “Create The SSH key pair” OR paste the content of id_rsa.pub (by pbcopy < ~/.ssh/id_rsa.pub command) which is also described at “Create The SSH key pair” section.

    Alternatively, we can also create a new SSH key pair(for example, appuser.pub) and add the content of the .pub file to the end of ~/.ssh/authorized_keys file. 

    1. Set the permissions to only allow this user to access it

    chmod 600 ~/.ssh/authorized_keys

    1. Run exit to go back to root and exit again to go back to local machine

     Stop The Root User and Password Login

    1. In the local machine run

    ssh appuser@hostname-you-like(setup in the ~/.ssh/config file) 

    1. We need to update the SSH configuration to disable password logins, and to disable logging in as root altogether. 

    sudo nano /etc/ssh/sshd_config

    1. Find PermitRootLogin yes and change it to PermitRootLogin no
    2. Find PasswordAuthentication yes and change it to PasswordAuthentication no
    3. Finally, restart the SSH service with this command:

    sudo systemctl reload sshd


    Set up a basic firewall

    Next, we’re going to configure a simple firewall. We’re going to configure it to deny all traffic except through standard web traffic ports (80 for HTTP, and 443 for HTTPS), and to allow SSH logins.

    1. Run the following commands

    sudo ufw allow OpenSSH

    sudo ufw allow http

    sudo ufw allow https

    sudo ufw enable

    1. Check the status by sudo ufw status

    Install Nginx

    1. Run[4], 

    sudo apt update

    sudo apt install nginx

    1. Now create a server block with the correct directives[4,5]. Instead of modifying the default configuration file directly, let’s make a new one at /etc/nginx/sites-available/myapp.com

    Run 

    sudo nano /etc/nginx/sites-available/myapp.com

    1. Paste in the following configuration block, which is similar to the default, but updated for our new directory and domain name[6]:

    server {

        server_name myapp.com;


        access_log /var/log/nginx/$host;


        location / {

            proxy_pass http://localhost:3000/;

            proxy_set_header X-Real-IP $remote_addr;

            proxy_set_header Host $host;

            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_set_header X-Forwarded-Proto https;

            proxy_redirect off;

        }


        error_page 502 /50x.html;

        location = /50x.html {

            root /usr/share/nginx/html;

        }

    }

    1. Restart Nginx by: sudo systemctl restart nginx

    Currently, there is nothing listening on this port, so we will get a 502 Bad Gateway or 404 Not Found error from Nginx. But If we follow the path described at section ‘Run The Application In Localhost But Use This Digital Ocean Server As A Tunnel’ (do not go there now), with this configuration in place, suppose we visited tunnel.yourdomain. Nginx will receive the connection, and see that it should reverse proxy it. It will effectively pass the connection on to whatever application is listening on port 3000. 

    Map The Domain In DNS Provider Console

    1. Log into your DNS provider. Add an A record (myapp.com or subdomain.myapp.com) for your domain that points to your droplet’s IP address.
    2. To check that the domain is pointing to your droplet, run the following:

    dig +short app.example.com

    # output should be your droplet’s IP address


    Install Let’s Encrypt and Generate a SSL Certificate

    1. The first step to using Let’s Encrypt to obtain an SSL certificate is to install the Certbot software on your server. First, add the repository:

    sudo add-apt-repository ppa:certbot/certbot

    1. Install Certbot’s Nginx package with apt:

    sudo apt install python-certbot-nginx

    1. Certbot provides a variety of ways to obtain SSL certificates through plugins. The Nginx plugin will take care of reconfiguring Nginx and reloading the config whenever necessary. To use this plugin, type the following:

    sudo certbot –nginx -d myapp.com -d subdomain.myapp.com

    This runs certbot with the –nginx plugin, using -d to specify the names we’d like the certificate to be valid for.

    The tool will run for a while to initialize itself, and then we’ll be asked for an admin email address, to agree to the terms, and to specify our domain name or names. Once that’s done, the certificate will be stored on the server for use with our app.


    1. Let’s Encrypt’s certificates are only valid for ninety days. This is to encourage users to automate their certificate renewal process. The certbot package we installed takes care of this for us by adding a renew script to /etc/cron.d. This script runs twice a day and will automatically renew any certificate that’s within thirty days of expiration.

    To test the renewal process, you can do a dry run with certbot:

    sudo certbot renew –dry-run

     

    Alternatively Install Let’s Encrypt and Generate a Stand Alone SSL Certificate(For advanced users only and in case the previous method does not work, because the certbot package is deprecated or some other reasons)

    1. To start, we need to install some tools that Let’s Encrypt depends on, then clone the letsencrypt repository to our server[3].

    sudo apt-get install bc

    1. Clone the Let’s Encrypt repository to your server

    sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt

    The tool will run for a while to initialize itself, and then we’ll be asked for an admin email address, to agree to the terms, and to specify our domain name or names. Once that’s done, the certificate will be stored on the server for use with our app.


    1. For security, Let’s Encrypt certificates expire every 90 days, which seems pretty short. (By contrast, most paid SSL certificates are valid for at least a year.)

    It turns out, though, that Let’s Encrypt has an one-step command to renew certificates:

    /opt/letsencrypt/certbot-auto renew

    This command checks if the certificate is near its expiration date and, when necessary, it generates an updated certificate that’s good for another 90 days.


    so we’re going to use a built-in tool called cron to handle the renewal automatically. 

    Please note that this auto renew part is configured automatically in case of using Nginx Plugin which is described in the previous section, instead of using standalone library which we are describing in this section. 


    To set this up, run the following command in the terminal to edit the server’s cron jobs:

    sudo crontab -e

    We get an option for which editor to use here. Since nano is easier than the others, we’ll stick with that.

    When the editor opens, head to the bottom of the file and add the following two lines:

    00 1 * * 1 /opt/letsencrypt/certbot-auto renew >> /var/log/letsencrypt-renewal.log

    30 1 * * 1 /bin/systemctl reload nginx

    The first line tells cron to run the renewal command, with the output logged so we can check on it when necessary, every Monday at 1 in the morning.

    The second restarts NGINX — which we haven’t set up yet, so don’t worry — at 1:30 to make sure the new cert is being used.

    Save and exit by pressing control + X, then Y, then enter.

    1. create a new file on our server to hold these settings — if we add another domain to this server, we can reuse them this way — which we’ll do with the following command:

    sudo nano /etc/nginx/snippets/ssl-params.conf

    Inside, we can copy-paste the following settings.

    # See https://cipherli.st/ for details on this configuration

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    ssl_prefer_server_ciphers on;

    ssl_ciphers “EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH”;

    ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0

    ssl_session_cache shared:SSL:10m;

    ssl_session_tickets off; # Requires nginx >= 1.5.9

    ssl_stapling on; # Requires nginx >= 1.3.7

    ssl_stapling_verify on; # Requires nginx => 1.3.7

    resolver 8.8.8.8 8.8.4.4 valid=300s;

    resolver_timeout 5s;

    add_header Strict-Transport-Security “max-age=63072000; includeSubDomains; preload”;

    add_header X-Frame-Options DENY;

    add_header X-Content-Type-Options nosniff;

    # Add our strong Diffie-Hellman group

    ssl_dhparam /etc/ssl/certs/dhparam.pem;


    Save and exit by pressing control + X, then Y, then enter.

    NOTE: The resolver parameter is set to Google’s DNS resolvers. This is part of OCSP stapling, which is a way to speed up certificate validation. If you have your own DNS resolver, you can update that value.

    1. Open the site configuration again. We configured it earlier when in “Install Nginx Section”.

    sudo nano /etc/nginx/sites-available/myapp.com

    Inside, change the existing content ro the following, as we need to now manually configure the certificate(in option 1 it was configured automatically via plugin. But now we are using a standalone certificate. So we need to configure it manually):

    # HTTPS — proxy all requests to the Node app

    server {

       # Enable HTTP/2

       listen 443 ssl http2;

       listen [::]:443 ssl http2;

       server_name app.example.com;

       # Use the Let’s Encrypt certificates

       ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;

       ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;

       # Include the SSL configuration from cipherli.st

       include snippets/ssl-params.conf;

       location / {

           proxy_set_header X-Real-IP $remote_addr;

           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

           proxy_set_header X-NginX-Proxy true;

           proxy_pass http://localhost:5000/;

           proxy_ssl_session_reuse off;

           proxy_set_header Host $http_host;

           proxy_cache_bypass $http_upgrade;

           proxy_redirect off;

       }

    }

    Save and exit by pressing control + X, then Y, then enter.

    1. Run sudo nginx -t. If It says OK. Then run,

    sudo systemctl start nginx


    Option 1: Run The Application In Localhost But Use This Digital Ocean Server As A Tunnel 

    [Option 2: Run The Application In This Digital Ocean Server And Use Visual Studio Code To Run And Modify Your code In the Server Via Remote Sync], described in later sections

    1. If you are using newly generated SSH key pair described “Create The SSH key pair” and you configure host, user, hostname and identity file (in ~/.ssh/config) then start your node.js application by npm run dev or npm start or nodemon start or node server.js (according to how you configure in your package.json) and then run the command,

    ssh -R 3000:localhost:3000 tunnel-user@tunnel-server


    Considering my config file has

    Host tunnel-server

     User tunnel-user

     Hostname server_ip

     IdentityFile ~/.ssh/ssh_key_file_name


    If you did not generate the file, rather just copy paste the existing id_rsa.pub file file by,

    pbcopy < ~/.ssh/id_rsa.pub in “Create The SSH key pair” section then just run,


    ssh -R 3000:localhost:3000 myapp.com

    Or

    ssh -R 3000:localhost:3000 subdomain.myapp.com

     

    So in general, 


    SSH reverse tunnelling port N to port K means making sshd listen on port N and effectively transfer incoming connections over the SSH connection to the SSH client. The SSH client will then transfer the connection to the application listening on port K on the client machine.

    Here’s the command to run on your client machine[6]:

    ssh -R N:localhost:K yourdomain


    An interactive session on your server should begin; while it is open, the reverse tunnel from port N to port K is active, and sshd will allow connections originating only from localhost, i.e. your server.

    Choosing N = 3000 will make it so Nginx reverse proxies incoming connections on tunnel.yourdomain into sshd, over the SSH connection, and into the application running on your local machine on port K.

    To test this out, on your local machine, in one shell run:

    python3 -m http.server 8888 


    and in another shell run:

    ssh -R 3000:localhost:8888 yourdomain


    Option 2: Run The Application In This Digital Ocean Server And Use Visual Studio Code To Run And Modify Your code In the Server Via Remote Development Using SSH

    1. You can follow the instructions here[7]. But basically after installing visual studio code and making sure that OpenSSH compatible SSH client (I am using mac where it is preinstalled), install Remote Development extension pack[8].
    2. Verify you can connect to the SSH host by running the following command from a terminal replacing user@hostname as appropriate.
    3. Remember to remove the root user config from the ~/.ssh/config file as we set up in the “Log In To The Development Machine” mentioned above. We stopped the root user login but we did not remove the config from ~/.ssh/config file. Now we have two entries for our myapp.com in the config file. We need to remove that or put it at the bottom of the file because, we will use the Remote Development extension and it will take the first configuration of the two entries and choose the user already set in that entry.
    4. In VS Code, press F1 to bring the command palette and select Remote-SSH: Connect to Host and use the host from the list. The list comes from the ~/.ssh/config File. 
    5. You will be logged in the home folder and create a folder called “app” or as you wish. 
    6. You can run any vscode command. For example, you can ‘cd’ into the folder and then code . It will open the folder in a new window as if it is opening it in your local machine. Do that.
    7. Now check if git is installed by ‘git –version’. If not installed, install it by sudo apt-get install git
    8. And then install nodejs and npm via sudo apt install nodejs and sudo apt install npm Command[9]. 
    9. Now Goto home folder by cd ~ and create an app folder like myapp or myapp.com or subdomain.myapp.com etc. by running mkdir myapp.

    cd ~

    mkdir myapp

    cd myapp

    1.  Run, npm init And do as it says in the prompt to create a package.js.
    2.  Create a index.js file and paste the following lines:

    ‘use strict’;

    // We’re setting up an extremely simple server here.

    const http = require(‘http’);

    // These could (should) be set as env vars.

    const port = process.env.PORT || 3000;

    const host = process.env.HOST || ‘localhost’;

    // No matter what hits the server, we send the same thing.

    http.createServer((req, res) => {

    // Tell the browser what’s coming.

    res.writeHead(200, {

    ‘Content-Type’: ‘text/html; charset=utf-8’,

    }); // Send a simple message in HTML.

    res.write(‘<h1>I’m a Node app!</h1>’);

    res.end();

    }).listen(port, host);

    // This message prints in the console when the app starts.

    console.log(`App running at http://${host}:${port}`);

    1.  Now run node index.js
    2.  Now if you visit myapp.com or subdomain.myapp.com that we mapped earlier you would see the out of the app. 
    3.  Simply starting the app manually is technically enough to get the app deployed, but if the server restarts, that means we have to manually start the app again. So we’re going to use a process manager called PM2 to run our app. PM2 also allows us to start the app automatically when the server restarts, which means one less thing we need to worry about. Run, 

    sudo npm install -g pm2

    In the terminal Visual Studio code that we are already using to ssh into and to development code inside the server.

    1. Now if you are already inside app folder run pm2 start index.js or just go inside the folder and run pm2 start index.js 
    2.  The last thing to do is to make sure that when the server restarts, PM2 starts our app again. Run, 

    pm2 startup

    Output:

    [PM2] Init System found: systemd

    [PM2] To setup the Startup Script, copy/paste the following command:

    sudo env PATH=$PATH:/usr/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup systemd -u tunnel-user –hp /home/tunnel-user

    In My case, it is tunnel-user. In Your case, it could be appuser, tunnel-user or something else depending on which user you set up in earlier sections. 

    1. So, As you can see PM2 prints out a command that we need to run using sudo. So, if you want your development environment as per option 2 that means always alive, Copy-paste that to finish the process. In my case it is, 

    sudo env PATH=$PATH:/usr/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup systemd -u tunnel-user –hp /home/tunnel-user

    1. In case you want to stick to stick to option 2 just run

    pm2 unstartup

    pm2 stop index.js

    Or If you did not setup the startup, just run 

    pm2 stop index.js and stick to option 1. 


    Ref.: 

    1. Create SSH Keys with OpenSSH: https://www.digitalocean.com/docs/droplets/how-to/add-ssh-keys/create-with-openssh/
    2. Upload SSH Public Keys to a DigitalOcean Account https://www.digitalocean.com/docs/droplets/how-to/add-ssh-keys/to-account/
    3. Node.js App to DigitalOcean with SSL: https://www.learnwithjason.dev/blog/deploy-nodejs-ssl-digitalocean/
    4. Install Ngix: https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-18-04
    5. How To Set Up Nginx Server Blocks: https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-server-blocks-virtual-hosts-on-ubuntu-16-04
    6. Self-hosted ngrok: https://www.digitalocean.com/community/questions/self-hosted-ngrok-or-serveo-alternative 
    7. Remote Development using SSH: https://code.visualstudio.com/docs/remote/ssh
    8. Remote Development Extension Pack: https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack
    9. Install Node.js on Ubuntu 18.04: https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-18-04