{"id":408,"date":"2025-04-17T22:28:15","date_gmt":"2025-04-17T16:58:15","guid":{"rendered":"https:\/\/yuvishere.co.in\/blog\/?p=408"},"modified":"2025-07-22T02:24:04","modified_gmt":"2025-07-21T20:54:04","slug":"deploying-multiple-applications-on-a-single-instance-using-aws-nginx-and-free-ssl-installation","status":"publish","type":"post","link":"https:\/\/yuvishere.co.in\/blog\/deploying-multiple-applications-on-a-single-instance-using-aws-nginx-and-free-ssl-installation\/","title":{"rendered":"Deploying multiple applications on a single instance using AWS, nginx and free SSL installation"},"content":{"rendered":"\n<p>This is a simple guide towards deploying multiple applications on a single virtual machine instance, like EC2 on AWS. For the sake of a running example, I will be talking about a Django application that I deployed for developmental \/ testing and production pipeline. This is not something that is set in stone, there could be more novel approaches to this, however, this is something that worked for me. The steps will remain similar for any other application, it will be indicated where you might have to change according to your own requirements. Though, I feel because of all these AI tools coming in, my blog will prove to be futile in the longer run. However, it\u2019s merely for cataloguing purposes so that I can always refer back to the processes to check if I have missed out on anything if I ever intend to do something similar in the future<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Code pipeline<\/h3>\n\n\n\n<p>Assuming that you are using GitHub to host your source code, I am providing the steps to setup the code pulling pipeline. Even with GitHub, there may be some different ways to execute this step, I am using a manual pull approach as I didn\u2019t have access to GitHub actions or any other tool at the time.<\/p>\n\n\n\n<p>To generate a valid SSH key for authenticating with GitHub, use the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh-keygen -t ed25519 -C \u201cyour@email.com\u201c<\/code><\/pre>\n\n\n\n<p>Make sure you replace <code>\u201cyour@email.com\u201d<\/code> with your registered email with GitHub. Enter a password for safety and save it somewhere in case you forget it. Now, you shall copy the key and provide it to GitHub so it can register it as a valid key. Copy and paste the output of the following code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat .ssh\/id_ed25519.pub<\/code><\/pre>\n\n\n\n<p>This is based on the assumption that you&#8217;re in the <code>user<\/code> directory. So, essentially, the <code>.ssh<\/code> folder is supposed to be in the &#8220;home&#8221; or &#8220;user&#8221; directory. The output is supposed to look something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh-ed25519 ADDDC3NzaD1lZCI1NTE5VAZAICmtA0p1hPVqKutVwCvCn8j24TZcSn2tj5IuXIDLLLna your@email.com<\/code><\/pre>\n\n\n\n<p>Now you can go ahead, create separate folders and clone your repositories. For my project, I created folders name <code>dev<\/code> and <code>prod<\/code> for developmental and production pipeline respectively. I clone the repository and branched to respective branches.<\/p>\n\n\n\n<p>You might have to run the following commands in case it is a fresh VM that you setup and doesn&#8217;t have the necessary libraries \/ software installed.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update &amp;&amp; sudo apt upgrade\nsudo apt install python3.12-venv nginx certbot python3-certbot-nginx -y<\/code><\/pre>\n\n\n\n<p>These commands will install the <code>venv<\/code> module for Python v3.12. Make sure you install for the version available on your machine.<\/p>\n\n\n\n<p>Once the repositories are setup, you&#8217;ve installed the packages and setup virtual environments and what not, it&#8217;s time to start deploying. A good idea would be to use some preliminary commands to test that your application is running as expected. I ran the following commands:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>python manage.py runserver<\/code><\/pre>\n\n\n\n<p>The application was running fine, after I had provided the correct <code>.env<\/code> file for the environment variables.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">nginx proxy<\/h3>\n\n\n\n<p>To deploy applications at scale, you will have to keep in mind security and accessibility for the same. I wanted to deploy on SSL using subdomains. For the sake of example, let&#8217;s say <code>backend.example.com<\/code> was used for production, and <code>dev.backend.example.com<\/code> was used for development. Assuming you are able to make things work for <code>backend.example.com<\/code>, you can alternatively use <code>devbackend.example.com<\/code> as well, but I was feeling adventurous so I added more depth to the subdomains.<\/p>\n\n\n\n<p>If you ran the previous commands, it is assumed <code>nginx<\/code> is installed. Now write the following to create the proxies.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nano \/etc\/nginx\/sites-available\/backend.example.com<\/code><\/pre>\n\n\n\n<p>Obviously, replace <code>backend.example.com<\/code> with your actual domain \/ subdomain name.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>server {\n    listen 80;\n    server_name backend.example.com;\n\n    location \/ {\n        proxy_pass http:\/\/localhost:5000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n\n    }\n}<\/code><\/pre>\n\n\n\n<p>This will allow you to serve the app that is running locally on the VM. Here, it is assumed that the app is running on <code>http:\/\/localhost:5000<\/code>. Make sure you change the value of the port accordingly.<\/p>\n\n\n\n<p>Once you have updated that file, save it and run the following commands to check if all is well.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nginx -t\nsudo systemctl reload nginx<\/code><\/pre>\n\n\n\n<p>Typically it should work just fine without errors. You might find yourself stumbling across some syntax error, it&#8217;s nothing that you can&#8217;t fix on your own. Trust me.<\/p>\n\n\n\n<p>You should repeat the same steps for your other domains, i.e. <code>dev.backend.example.com<\/code> or <code>devbackend.example.com<\/code> or <code>whatever.example.com<\/code><\/p>\n\n\n\n<p>Lastly, we need to create a symbolic link to <code>sites-enabled<\/code> after having created the said file. Run the following command for the same:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo ln -s \/etc\/nginx\/sites-available\/backend.example.com \/etc\/nginx\/sites-enabled\/<\/code><\/pre>\n\n\n\n<p>After all this, don&#8217;t forget to point the domain to your VM. You can do so by adding an <code>A<\/code> record to your DNS for the domain for the given subdomain (<code>backend<\/code> in this case). The value should be the public IP address of the VM. Make sure it is the public IP address, and if you are using AWS, make sure you&#8217;ve linked an elastic IP address. This comes in handy when you have to restart your VM instance for whatever reason. It saves you the trouble of updating the IP addresses in the DNS, because without the elastic IP address, the instance is assigned a different public IP.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Daemon<\/h3>\n\n\n\n<p>Daemons are programs which run in the background. Since, we want the application to persist and run even when the SSH session has been terminated, we shall write a small script to do so. Run this command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nano \/etc\/systemd\/system\/prodbackend.service<\/code><\/pre>\n\n\n\n<p>This particular script is written assuming that your app is running for the production pipeline (<code>backend.example.com<\/code>).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;Unit]\nDescription=Production backend on port 5000\nAfter=network.target\n\n&#91;Service]\nUser=root\nGroup=www-data\nWorkingDirectory=\/home\/ubuntu\/path\/to\/your\/backend\/folder\/\nExecStart=\/home\/ubuntu\/path\/to\/your\/backend\/folder\/.venv\/bin\/gunicorn -- workers 3 -- bind 0.0.0.0:5000 myApplication.wsgi: application\nRestart=always\n\n&#91;Install]\nWantedBy=multi-user. target<\/code><\/pre>\n\n\n\n<p>It maybe a lot to absorb, but it&#8217;s easy when you read through it a couple of times. Firstly, update the <code>\/home\/ubuntu\/path\/to\/your\/backend\/folder\/<\/code> to your actual path to the backend folder. Secondly, I am using <code>.venv<\/code> as my virtual environment folder, you may have set it up differently \/ named it differently, make sure you add the path to the <code>gunicorn<\/code> or <code>uvicorn<\/code> executable binary file (assuming your application uses it). Lastly, make sure you are binding the application at the correct port. It should match with what you had provided to <code>nginx<\/code> for that particular domain.<\/p>\n\n\n\n<p>After making these necessary edits, save the file and run the following commands:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl reload systemd\nsudo systemctl daemon-reload\nsudo systemctl start prodbackend\nsudo systemctl status prodbackend<\/code><\/pre>\n\n\n\n<p>Make sure that you&#8217;ve written the same name as the argument as you did while creating the daemon file.<\/p>\n\n\n\n<p>You can periodically run the <code>sudo systemctl status prodbackend<\/code> to check the state of your application, i.e. see if it is crashing or needs a restart, or check the most recent logs.<\/p>\n\n\n\n<p>Repeat the same steps for the other domain \/ subdomain.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">SSL<\/h3>\n\n\n\n<p>Something that drives everyone crazy at some point if not done correctly. Make sure you&#8217;ve added the following DNS records:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CAA \"0 issue letsencrypt.org\" for @ or subdomain\nCAA \"0 issuewild letsencrypt.org\" for @ or subdomain<\/code><\/pre>\n\n\n\n<p>This is something you might have to check with your DNS provider, but in general you need 2 records for your subdomains \/ apex domain, one with the value <code>\"0 issue letsencrypt.org\"<\/code> and another with <code>\"0 issuewild letsencrypt.org\"<\/code>.<\/p>\n\n\n\n<p>Next steps are easier on the mind. Just run the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo certbot --nginx -d backend.example.com<\/code><\/pre>\n\n\n\n<p>Just follow the steps that appear on your console. If everything is alright, then it shall deploy successfully. You can verify the same by opening the <code>\/etc\/nginx\/sites-available\/backend.example.com<\/code> file. It shall have another clause mentioning details about the SSL and redirecting to the port 443, which is used for SSL connections.<\/p>\n\n\n\n<p>You shall repeat the same steps for your other domains \/ subdomains.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Conclusion<\/h3>\n\n\n\n<p>That&#8217;s it. Happy to help if you&#8217;re stuck at some point. I always recommend learning on your own, experimenting with things (safely), that&#8217;s where real education lies. However, nothing beats some good guidance when you need it. Don&#8217;t be afraid to ask. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is a simple guide towards deploying multiple applications on a single virtual machine instance, like EC2 on AWS. For the sake of a running example, I will be talking about a Django application that I deployed for developmental \/ testing and production pipeline. This is not something that is set in stone, there could [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[38,37,41,42,40,36,39],"class_list":["post-408","post","type-post","status-publish","format-standard","hentry","category-technology","tag-aws","tag-deployment","tag-django","tag-dns","tag-nginx","tag-ssl","tag-vm"],"_links":{"self":[{"href":"https:\/\/yuvishere.co.in\/blog\/wp-json\/wp\/v2\/posts\/408","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/yuvishere.co.in\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/yuvishere.co.in\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/yuvishere.co.in\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/yuvishere.co.in\/blog\/wp-json\/wp\/v2\/comments?post=408"}],"version-history":[{"count":6,"href":"https:\/\/yuvishere.co.in\/blog\/wp-json\/wp\/v2\/posts\/408\/revisions"}],"predecessor-version":[{"id":422,"href":"https:\/\/yuvishere.co.in\/blog\/wp-json\/wp\/v2\/posts\/408\/revisions\/422"}],"wp:attachment":[{"href":"https:\/\/yuvishere.co.in\/blog\/wp-json\/wp\/v2\/media?parent=408"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/yuvishere.co.in\/blog\/wp-json\/wp\/v2\/categories?post=408"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/yuvishere.co.in\/blog\/wp-json\/wp\/v2\/tags?post=408"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}