{"id":596,"date":"2020-03-10T01:49:22","date_gmt":"2020-03-10T01:49:22","guid":{"rendered":"https:\/\/www.antpace.com\/blog\/?p=596"},"modified":"2025-08-25T14:02:35","modified_gmt":"2025-08-25T14:02:35","slug":"website-on-aws-ec2","status":"publish","type":"post","link":"https:\/\/www.antpace.com\/blog\/website-on-aws-ec2\/","title":{"rendered":"Launching a website on AWS EC2"},"content":{"rendered":"<p>In 2008 I deployed my first website to production. It used a simple LAMP stack , a GoDaddy domain name, and HostGator hosting.<\/p>\n<p>Since 2016, I&#8217;ve used AWS as my primary cloud provider. And this year, I&#8217;m finally cancelling my HostGator package. Looking through that old server, I found artifacts of past projects &#8211; small businesses and start-ups that I helped develop and grow. A virtual memory lane.<\/p>\n<p>Left on that old box was a site that I needed to move to a fresh EC2 instance. This is an opportunity to document how I launch a site to Amazon Web Services.<\/p>\n<h2>Amazon Elastic Compute Cloud<\/h2>\n<p>To start, I launch a new EC2 instance from the AWS console. Amazon&#8217;s Elastic Compute Cloud provides &#8220;<a href=\"https:\/\/aws.amazon.com\/ec2\/\">secure and resizable compute capacity in the cloud<\/a>.&#8221; When prompted to choose an Amazon Machine Image (AMI), I select &#8220;Amazon Linux 2 AMI&#8221;. I leave all other settings as default. When I finally click &#8220;Launch&#8221;, it&#8217;ll ask me to either generate a new key file, or use an existing one. I&#8217;ll need that file later to SSH or sFTP into this instance. A basic Linux server is spun up, with little else installed.<\/p>\n<figure id=\"attachment_633\" aria-describedby=\"caption-attachment-633\" style=\"width: 1342px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-633 size-full\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/04\/linux-ami.png\" alt=\"Linux AMI\" width=\"1342\" height=\"292\" \/><figcaption id=\"caption-attachment-633\" class=\"wp-caption-text\">Amazon Linux 2 AMI is free tier eligible.<\/figcaption><\/figure>\n<p>Next, I make sure that instance&#8217;s <a href=\"https:\/\/docs.aws.amazon.com\/AWSEC2\/latest\/UserGuide\/ec2-security-groups.html\">Security Group<\/a> allows inbound traffic on SSH, HTTP, and HTTPS. We allow all traffic via HTTP and HTTPS (IPv4 and IPv6, which is why there are 2 entries for each). That way end-users can reach the website from a browser. <strong>Inbound SSH access should not be left wide open.<\/strong> Only specific IP addresses should be allowed to command-line in to the server. AWS has an option labeled &#8220;My IP&#8221; that will populate it for your machine.<\/p>\n<figure id=\"attachment_634\" aria-describedby=\"caption-attachment-634\" style=\"width: 1286px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-634\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/04\/security-group-inbound-rules.png\" alt=\"Inbound security rules\" width=\"1286\" height=\"712\" \/><figcaption id=\"caption-attachment-634\" class=\"wp-caption-text\">Don&#8217;t allow all IPs to access SSH in a live production environment.<\/figcaption><\/figure>\n<p>Recent AWS UI updates let you set these common rules directly from the &#8220;Launch an instance&#8221; screen, under &#8220;Network settings&#8221;<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1862\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/03\/network-settings.png\" alt=\"AWS Network settings screen\" width=\"851\" height=\"593\" \/><\/p>\n<h2>Configure the server<\/h2>\n<p>Now that the hosting server is up-and-running, I can command-line in via SSH from my Mac&#8217;s terminal using the key file from before. This is what the command looks like:<\/p>\n<pre> ssh -i \/Users\/apace\/my-keys\/keypair-secret.pem ec2-user@ec2-XXX-XXX-XX.us-east-2.compute.amazonaws.com<\/pre>\n<p>You might get a CLI error that says:<\/p>\n<p class=\"p1\"><strong><span class=\"s1\">&#8221; It is required that your private key files are NOT accessible by others. <\/span><\/strong><strong><span class=\"s1\">This private key will be ignored. &#8220;<\/span><\/strong><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1863\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/03\/bad-permissions.png\" alt=\"Bad permissions warning against a pem key file in the CLI\" width=\"858\" height=\"163\" \/><\/p>\n<p>That just means you need to update the file permissions on the key file. You should do that from the command line, in the directory where the file resides:<\/p>\n<pre>chmod 400 KeyFileNameHere.pem<\/pre>\n<p>Make sure everything is up-to-date by running &#8220;<em>sudo yum update<\/em>&#8220;.\u00a0 I begin installing the required software to host a website:<\/p>\n<pre>sudo amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2<\/pre>\n<p><strong>Note:<\/strong> <code>amazon-linux-extras<\/code> <a href=\"https:\/\/aws.amazon.com\/linux\/amazon-linux-2023\/faqs\/\" target=\"_blank\" rel=\"noopener\">doesn&#8217;t exist on the Amazon Linux 2023<\/a> image.<\/p>\n<p>That command gives me Apache, PHP, and MariaDB &#8211; a basic LAMP stack. This next one installs the database server:<\/p>\n<pre>sudo yum install -y httpd mariadb-server<\/pre>\n<p>MariaDB is a fork of the typical MySQL, but with better performance.<\/p>\n<p>By default, MariaDB will not have any password set. If you choose <a href=\"https:\/\/www.antpace.com\/blog\/migrate-a-wordpress-site-to-aws#phpMyAdmin\" target=\"_blank\" rel=\"noopener\">to install phpMyAdmin<\/a>, it won&#8217;t let you login without a password (as per a default setting). You&#8217;ll have to set a password from the command line. While connected to your instance via SSH, dispatch this command:<\/p>\n<pre>mysql -u root -p<\/pre>\n<p>When it prompts you for a password, just hit enter.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1867\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/03\/mariadb.png\" alt=\"login to mariadb for the first time\" width=\"728\" height=\"207\" \/><\/p>\n<p>Once you&#8217;re logged in, you need to switch to the <code>mysql<\/code> database by running the following command:<\/p>\n<pre>use mysql;<\/pre>\n<p>Now you can set a password for the root user with the following command:<\/p>\n<pre>UPDATE user SET password = PASSWORD('new_password') WHERE user = 'root';\n<\/pre>\n<p>After setting the password, you need to flush the privileges to apply the changes:<\/p>\n<pre>FLUSH PRIVILEGES;<\/pre>\n<p>Start Apache: &#8220;<em>sudo systemctl start httpd<\/em>&#8220;. And, make sure it always starts when the server boots up &#8220;<em>sudo systemctl enable httpd<\/em>&#8221;<\/p>\n<p>The server setup is complete. I can access an Apache test page from a web browser by navigating to the EC2 instance&#8217;s public IP address.<\/p>\n<figure id=\"attachment_642\" aria-describedby=\"caption-attachment-642\" style=\"width: 1361px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-642\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/04\/apache-test-page-1.png\" alt=\"Apache test page\" width=\"1361\" height=\"425\" \/><figcaption id=\"caption-attachment-642\" class=\"wp-caption-text\">A test page shows when no website files are present.<\/figcaption><\/figure>\n<p>I&#8217;ll take my website files (that are stored on my local machine and synched to a Git repo) and copy them to the server via sFTP.<\/p>\n<figure id=\"attachment_640\" aria-describedby=\"caption-attachment-640\" style=\"width: 1006px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-640\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/04\/ftp-copy-files.png\" alt=\"Copy website files to server\" width=\"1006\" height=\"650\" \/><figcaption id=\"caption-attachment-640\" class=\"wp-caption-text\">I use FileZilla to access my EC2 public directory<\/figcaption><\/figure>\n<p>I need to make sure the Linux user I sFTP with owns the directory &#8220;\/var\/www\/html&#8221;, or else I&#8217;ll get a permission denied error:<\/p>\n<p><em>sudo chown -R ec2-user \/var\/www\/html<\/em><\/p>\n<p>Later, if I want to be able to upload media to the server from the WordPress CMS, I&#8217;ll need to be sure to change the owner of the blog&#8217;s directory to the\u00a0<em>apache<\/em> user (which is the behind-the-scenes daemon user invoked for such things):<\/p>\n<p><em>sudo chown -R apache \/var\/www\/html\/blog<\/em><\/p>\n<p>* <a href=\"https:\/\/docs.aws.amazon.com\/AWSEC2\/latest\/UserGuide\/ec2-lamp-amazon-linux-2.html\" target=\"_blank\" rel=\"noopener\">AWS Documentation Reference<\/a><\/p>\n<h2>Domain name and Route 53<\/h2>\n<p>Instead of having to use the EC2 server&#8217;s public address to see my website from a browser, I&#8217;ll point a domain name at it. <a href=\"https:\/\/aws.amazon.com\/route53\/\">AWS Route 53<\/a> helps with this. It&#8217;s a &#8220;DNS web service&#8221; that routes users to websites by mapping domain names to IP addresses.<\/p>\n<p>In Route 53 I create a new &#8220;hosted zone&#8221;, and enter the domain name that I&#8217;ll be using for this site.\u00a0 This will automatically generate two record sets: a Name Server\u00a0(NS) record and a Start-of-Authority (SOA) record. I&#8217;ll create one more, an IPv4 address (A) record. The value of that record should be the public IP address that I want my domain to point at. You&#8217;ll probably also want to add another, identical to the last one, but specifying &#8220;www&#8221; in the record name.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-648 size-full\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/04\/route-53-record-set-1.png\" alt=\"setting a record in AWS\" width=\"1015\" height=\"477\" \/><\/p>\n<p>Finally, I&#8217;ll head over to my domain name registrar, and find my domain name&#8217;s settings. I update the nameserver values there to match those in my Route 53 NS record set. It&#8217;ll likely take some time for this change to be reflected in the domain&#8217;s settings. Once that is complete, the domain name will be pointing at my new EC2 instance.<\/p>\n<p>And that&#8217;s it &#8211; I&#8217;ve deployed my website to AWS. <a href=\"https:\/\/www.antpace.com\/blog\/secure-a-website-with-ssl-and-https-on-aws\/\">The next step is to secure my site by configuring SSL and HTTPS<\/a>.<\/p>\n<p><a href=\"https:\/\/www.antpace.com\/blog\/migrate-a-wordpress-site-to-aws\/\">If you&#8217;re migrating a dynamic WordPress site, you&#8217;ll require a some additional steps to complete the migration<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In 2008 I deployed my first website to production. It used a simple LAMP stack , a GoDaddy domain name, and HostGator hosting. Since 2016, I&#8217;ve used AWS as my primary cloud provider. And this year, I&#8217;m finally cancelling my HostGator package. Looking through that old server, I found artifacts of past projects &#8211; small &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.antpace.com\/blog\/website-on-aws-ec2\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Launching a website on AWS EC2&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":3165,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[12,23,42,74,79,92,110],"class_list":["post-596","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-development","tag-aws","tag-cloud","tag-ec2","tag-lamp","tag-mariadb","tag-php","tag-route-53"],"_links":{"self":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/596","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/comments?post=596"}],"version-history":[{"count":1,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/596\/revisions"}],"predecessor-version":[{"id":3166,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/596\/revisions\/3166"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media\/3165"}],"wp:attachment":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media?parent=596"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/categories?post=596"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/tags?post=596"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}