Build and Deploy a History Website

Serve a multi-section history site from your own Ubuntu server via Nginx.

Introduction

30 Second Summary

Think about museums or history documentaries. They use dramatic imagery to bring the past to life, making you feel like you are standing in ancient ruins or watching the Industrial Revolution unfold. A well-designed website can create that same immersive experience for anyone with a link.

In this project, you will build a scrolling "Journey Through History" timeline website with full-screen background images for each era, deploy it to your own Ubuntu server using Nginx, and add a PDF documents library where visitors can view and download files directly in the browser.

What You'll Build

Picture sharing your server's IP address with a friend. They type it into their browser and scroll through immersive full-screen historical photographs, reading about each era over darkened backgrounds, then browse and download PDF documents at the end.

By the end of this project, you'll have:

  • Full-viewport historical sections that each fill the entire screen with a dramatic public domain background image and clearly readable text on top, using a CSS overlay technique.
  • A live public website served from your own Ubuntu server via Nginx, accessible to anyone in the world who has your server's IP address.
  • A PDF documents library where visitors can view embedded PDFs inline in the browser or download them with a single click.
  • Secret Mission: Add a fixed navigation bar with smooth scrolling between sections.

Are there any prerequisites?

None. This guide walks you through every tool installation and setup step from scratch, including Nginx and file transfers via SCP. All you need is a local Ubuntu machine with VS Code and SSH access to a fresh Ubuntu server.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Install Nginx on Your Server

Your server is a fresh Ubuntu machine. Right now, nobody can visit it with a browser because there is no software listening for web requests.

To make your server respond to browsers, you need a web server. In this step, you'll install Nginx on your server and confirm it's publicly accessible.

In this step, get ready to:
  • Install and configure Nginx on your server.
  • Confirm the default Nginx page loads in your browser.
SSH into your server and install Nginx

SSH lets you control a remote server from your local terminal. You'll use it to run commands on the server as if you were sitting right in front of it.

  • In your terminal, connect to your server by running this command:
ssh [[SERVER_USER="your-server-username"]]@[[SERVER_IP="your-server-ip-address"]]

Connection refused or timed out?

Make sure the server is powered on and connected to the network. Double-check that you typed the correct IP address and username.

If you have physical access, you can log in directly at the server's keyboard and monitor.

help me troubleshoot my SSH connection

Once connected, your terminal prompt changes to show the server's hostname. You're now running commands on the remote server.

  • Update your package list and install Nginx by running this command:
sudo apt update && sudo apt install nginx

What does this command do?

  • sudo apt update refreshes the list of available packages so you get the latest version.
  • sudo apt install nginx downloads and installs the Nginx web server.
  • The && runs the second command only if the first one succeeds.

On Ubuntu, Nginx automatically starts running as soon as it finishes installing.

Seeing a Y/n prompt?

The installer asks you to confirm the download. Type Y and press Enter to continue.

You'll see a stream of output as packages download and install. When it finishes, your terminal returns to the normal prompt.

  • Enable Nginx to start automatically on boot by running:
sudo systemctl enable nginx
  • Allow web traffic through the firewall by running:
sudo ufw allow 'Nginx HTTP'

What do these commands do?

systemctl enable tells Ubuntu to start Nginx automatically every time the server reboots. Without this, you'd have to manually start it after each restart.

ufw allow 'Nginx HTTP' opens port 80 in the firewall so browsers can reach your server. UFW (Uncomplicated Firewall) blocks most incoming traffic by default to keep your server secure.

  • Confirm Nginx is running by checking its status:
sudo systemctl status nginx

You should see green text reading active (running) in the output. This confirms Nginx is installed and serving requests.

Status shows inactive or failed?

Try restarting Nginx manually:

sudo systemctl restart nginx

Then check the status again. If it still fails, look for error messages in the output that mention port conflicts or configuration issues.

help me fix my Nginx service

Confirm Nginx is accessible from your browser

Nginx is running on the server, but you need to prove that the outside world can reach it. The true test is loading the page from a different machine.

Before you try this, what do you think you'll see when you visit your server's IP in a browser?

  • On your local machine, open any web browser.
  • Type http:// followed by your server's IP address into the address bar and press Enter.

You should see the default Nginx welcome page with the heading "Welcome to nginx!" in large text. This confirms your server is live and responding to browser requests from across the network.

Page won't load or times out?

  • Make sure you typed http:// (not https://) before the IP address. HTTPS is not configured yet.
  • Verify the IP address matches the one you used to SSH in.
  • Check that the firewall rule was added successfully by running sudo ufw status on the server. You should see a line mentioning Nginx HTTP with ALLOW.

help me access my Nginx server from a browser

Your web server is live and ready to serve pages. Next up, you'll build your first historical section with a dramatic background image and deploy it to the world.

Build Your First Historical Section and Deploy It

Your server is running, but it is still showing the default Nginx welcome page. You need to replace that with your own content.

A single full-screen section with a dramatic historical background image gives you something live on the internet immediately. In this step, you will build a hero section locally and push it to your server.

In this step, get ready to:
  • Create the project HTML and CSS files on your local machine.
  • Download a public domain historical image for the background.
  • Deploy the files to your server and see your site live.
Create the project folder and write the HTML

You will build the site on your local Ubuntu machine first, then copy it to the server. This keeps your workflow clean.

  • Open a new terminal window on your local machine (not the SSH session to your server).
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create the project folder with an images subdirectory and move into it by running these commands:
mkdir -p history-site/images
cd history-site
  • Confirm the folder structure exists by running:
ls

You should see the images folder listed in the output.

  • Open this folder in VS Code by running:
code .

VS Code did not open?

If you see a "command not found" error, open VS Code manually. Press the Super key (the Windows key on your keyboard) to open the app search bar. Type VS Code and press Enter to launch it. Then click File then Open Folder and select the history-site folder on your Desktop.

help me set up the code command for VS Code on Ubuntu

  • Create a new file called index.html in VS Code by clicking File then New File.
  • Copy the following HTML into index.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Journey Through History</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <section class="era" id="hero">
        <div class="content">
            <h1>Journey Through History</h1>
            <p>Explore the ages that shaped our world</p>
        </div>
    </section>
</body>
</html>

What does this HTML do?

  • The <section class="era" id="hero"> creates a full-page container for your first historical era.
  • The .content div wraps the text so you can position it with CSS later.
  • The stylesheet link points to style.css which you will create next.
  • Save index.html by pressing Ctrl+S.
  • Open index.html in your browser to check it by running this command in your terminal:
xdg-open index.html

You should see the text "Journey Through History" and "Explore the ages that shaped our world" in your browser. It looks plain because there is no CSS yet.

Style the hero section with CSS

CSS will make the section fill the entire screen and prepare it to display a background image behind the text.

  • Create a new file called style.css in VS Code by clicking File then New File.
  • Add the page structure styles to style.css:
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: Georgia, 'Times New Roman', serif;
    color: #ffffff;
}

.era {
    position: relative;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    background-attachment: fixed;
}

What does this CSS do?

  • The * reset removes default browser margins and padding so your layout is predictable.
  • Setting color: #ffffff on the body makes all text white, which stands out against dark background images.
  • The .era class uses min-height: 100vh to fill the entire viewport height. The background-size: cover ensures a background image scales to fill the section without distortion.
  • Now add the content styling and hero background reference below the .era block in the same file:
.content {
    position: relative;
    z-index: 1;
    max-width: 800px;
    padding: 2rem;
    text-align: center;
}

.content h1 {
    font-size: 3.5rem;
    margin-bottom: 1rem;
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
}

.content p {
    font-size: 1.2rem;
    line-height: 1.8;
    text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8);
}

#hero {
    background-image: url('images/hero.jpg');
}

What does this CSS do?

  • The .content rules center the text and give it breathing room with padding.
  • The text-shadow adds a dark glow behind each letter, improving readability against lighter backgrounds.
  • The #hero rule tells the browser to load images/hero.jpg as the background. You will download this image next.
  • Save style.css by pressing Ctrl+S.
  • Refresh your browser tab. The section now fills the full viewport height with white centered text. The background is black because the image file does not exist yet.

✔️ Awesome, I've got everything!

Great. Double check you have saved both files before moving on.

ⓧ I'd like to double check the full code

Scroll up and confirm your index.html matches the HTML chunk above, and your style.css contains both CSS chunks in order (the page structure styles followed by the content styling and hero background reference).

Download a public domain historical image

You need a dramatic historical photograph for the background. Public domain image collections provide free images with no licensing restrictions.

  • Visit the Library of Congress free-to-use collection in your browser:

https://www.loc.gov/free-to-use/

  • Browse for a historical image you like (ancient ruins, old maps, historical photographs).
  • Right-click the image and select Copy Image Address to copy its URL.
  • In your terminal, download the image and save it as hero.jpg by running this command (replace the URL with the one you copied):
wget -O images/hero.jpg [[IMAGE_URL="the image URL you copied"]]

What does this command do?

  • The -O flag tells wget to save the downloaded file with a specific name and path.
  • This saves the image directly into your images/ folder as hero.jpg, matching what style.css expects.
  • Confirm the image downloaded by running:
ls images/

You should see hero.jpg listed in the output.

  • Refresh your browser tab. You should now see your historical image filling the entire viewport with white text in the center.

Background still black?

Make sure the file is named exactly hero.jpg inside the images folder. Check with ls images/ in your terminal. If you see a different name or extension, rename it to match.

help me debug why my CSS background image is not loading

Deploy the files to your server

Your site works locally. Now you will copy it to the server so anyone with the IP address can see it.

  • From your local terminal (make sure you are in the history-site folder), copy all project files to the server's web root using scp:
scp -r ./* [[SERVER_USER="your-username"]]@[[SERVER_IP="your-server-ip"]]:/tmp/history-site/

Why copy to /tmp first?

The Nginx web root at /var/www/html/ is owned by root. Copying to /tmp/ first avoids permission errors during the transfer. You will move the files into place on the server next.

  • Switch to your SSH session on the server (the terminal where you are logged into the server).
  • Remove the default Nginx page and copy your files into the web root by running these commands:
sudo rm -rf /var/www/html/*
sudo cp -r /tmp/history-site/* /var/www/html/
  • Set the correct ownership so Nginx can read the files by running:
sudo chown -R www-data:www-data /var/www/html

What does chown do here?

Nginx runs as the www-data user. If the files are owned by your user account, Nginx might not have permission to read them. The -R flag applies the change recursively to all files and subdirectories.

  • Confirm the files are in place by running:
ls /var/www/html/

You should see index.html, style.css, and the images folder listed.

Before you open the site, what do you think the page will look like? Will the text be easy to read on top of the photograph?

  • Open a browser on your local machine and visit your server's IP address:
http://[[SERVER_IP="your-server-ip"]]

You see your historical background image filling the entire viewport with white text on top. The text is difficult to read because the busy photograph behind it competes for attention. That is expected. In the next step, you will fix this with a CSS overlay technique.

Seeing a 403 Forbidden error?

This means Nginx cannot read your files. Run sudo chown -R www-data:www-data /var/www/html again on the server and refresh the page.

If you still see the default Nginx welcome page, your index.html might not have been copied correctly. Run ls /var/www/html/ and confirm you see your files listed.

help me debug why my Nginx server shows a 403 error instead of my website

Your first section is live on the internet. Next up, you will fix the readability problem and build out the full timeline with multiple historical eras.

Fix Readability and Build the Full Timeline

Your hero section is live, but the text is fighting the busy photograph behind it. You need a way to darken the image without modifying the image file itself.

The solution is a CSS overlay. You will place a semi-transparent black layer between the background image and your text. Once you have that working, you can confidently expand the page into a full scrolling timeline with five distinct historical eras.

In this step, get ready to:
  • Add a CSS overlay to darken background images behind text.
  • Expand the timeline to five historical era sections with unique images.
  • Redeploy the full site to your server and verify everything displays correctly.
Add the overlay to fix text readability

An overlay is an invisible-looking layer that sits between the background image and the text content. By making it semi-transparent black, the image darkens and white text becomes perfectly readable.

  • In VS Code, open index.html from your project folder.
  • Find the opening <section class="era" id="hero"> tag.
  • Add a new <div class="overlay"></div> line directly after it, so the hero section looks like this:
<section class="era" id="hero">
    <div class="overlay"></div>
    <div class="content">
        <h1>Journey Through History</h1>
        <p>Explore the ages that shaped our world</p>
    </div>
</section>

What does the overlay div do?

The .overlay div is an empty element that will stretch over the entire section using CSS absolute positioning. Because it sits between the background image and the .content div, it darkens the photo without touching the image file.

  • Save index.html.
  • Now open style.css and add the overlay styles by pasting the following code after the closing } of the .era rule:
/* Dark overlay to make text readable on busy images */
.overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.55);
}

How does the overlay work?

The position: absolute stretches the overlay to fill the entire section (because .era is position: relative).

The rgba(0, 0, 0, 0.55) gives it a 55% opaque black fill, which dims the image beneath. Meanwhile the .content div already has z-index: 1 in your CSS, placing the text above the overlay.

  • Save style.css.
  • Deploy the two updated files to your server by running this command:
scp index.html style.css [[SERVER_USER="your-username"]]@[[SERVER_IP="your server IP address"]]:/var/www/html/
  • Switch to your browser and refresh http://your server IP address.

The hero section text now sits on a darkened background. "Journey Through History" and the paragraph beneath it are crisp white against the dimmed photo.

Text still hard to read?

  • Make sure the <div class="overlay"></div> line is inside the <section> tag and before the <div class="content"> div, not after it.
  • Confirm your .overlay CSS rule is outside the .era block (after its closing }), not nested inside it.
  • Check that the class name in your HTML matches exactly: overlay (no typos).

Still not working? Help me debug why my CSS overlay is not darkening the background image.

Download images and expand the timeline

With the overlay proven on the hero section, you can now build out the full timeline. Each new era section uses the same overlay pattern, so text will be readable everywhere.

  • ancient.jpg (ancient ruins, pyramids, or classical architecture).
  • medieval.jpg (castles, manuscripts, or medieval scenes).
  • renaissance.jpg (Renaissance paintings or architecture).
  • industrial.jpg (factories, railways, or steam engines).
  • documents.jpg (old archives, scrolls, or a library).
  • Confirm all six images exist by running:
ls images/

You should see ancient.jpg, documents.jpg, hero.jpg, industrial.jpg, medieval.jpg, and renaissance.jpg listed.

Choose landscape images

Landscape-oriented images work best with background-size: cover because sections are wider than they are tall. Portrait images will be cropped heavily on the sides.

  • Return to index.html in VS Code.
  • Find the closing </section> tag of the hero section.
  • Add the following three era sections directly below it:
    <section class="era" id="ancient">
        <div class="overlay"></div>
        <div class="content">
            <h2>The Ancient World</h2>
            <p>From the rise of early civilizations along the Nile and Mesopotamia to the great empires of Greece and Rome, the ancient world laid the foundations of law, philosophy, and architecture that still influence us today.</p>
        </div>
    </section>

    <section class="era" id="medieval">
        <div class="overlay"></div>
        <div class="content">
            <h2>The Medieval Era</h2>
            <p>Castles, cathedrals, and conquests defined a thousand years of human history. The medieval period saw the spread of religions, the rise of feudalism, and the seeds of the Renaissance yet to come.</p>
        </div>
    </section>

    <section class="era" id="renaissance">
        <div class="overlay"></div>
        <div class="content">
            <h2>The Renaissance</h2>
            <p>A rebirth of art, science, and thought swept across Europe. From Leonardo da Vinci to Gutenberg's printing press, humanity rediscovered its potential and reshaped the world.</p>
        </div>
    </section>

What does each section contain?

Every section follows the same three-part pattern: a .era wrapper with a unique id, the .overlay div for the darkening effect, and a .content div holding the heading and paragraph text.

The id attribute on each section links it to a specific background-image rule in CSS.

  • Directly below the Renaissance section you just added, paste the remaining sections and a footer:
    <section class="era" id="industrial">
        <div class="overlay"></div>
        <div class="content">
            <h2>The Industrial Revolution</h2>
            <p>Steam engines, factories, and railways transformed agrarian societies into industrial powerhouses. This era of rapid change brought unprecedented progress and profound social upheaval.</p>
        </div>
    </section>

    <section class="era" id="documents">
        <div class="overlay"></div>
        <div class="content">
            <h2>Historical Documents</h2>
            <p>Browse and download primary source documents from the archives.</p>
        </div>
    </section>

    <footer>
        <p>Images sourced from the public domain via Wikimedia Commons and the Library of Congress.</p>
    </footer>

Why a documents section with no documents yet?

The documents section is a placeholder for now. You will fill it with embedded PDFs and download links in the next step. Including it now means the full page structure is complete.

  • Save index.html.
  • Now switch to style.css and add the following CSS after the #hero rule. This adds the h2 heading style, smooth scrolling, and background-image rules for every new section:
/* Smooth scrolling for anchor links */
html {
    scroll-behavior: smooth;
}

/* Heading style for era sections */
.content h2 {
    font-size: 2.5rem;
    margin-bottom: 1rem;
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
}

What does scroll-behavior: smooth do?

When a visitor clicks an anchor link (like #ancient), the browser animates the scroll instead of jumping instantly. You will use this later, but adding it now keeps your CSS organised.

  • Below the existing #hero background-image rule, add a rule for each new section plus the footer by pasting this code:
/* Background images for each era */
#ancient {
    background-image: url('images/ancient.jpg');
}

#medieval {
    background-image: url('images/medieval.jpg');
}

#renaissance {
    background-image: url('images/renaissance.jpg');
}

#industrial {
    background-image: url('images/industrial.jpg');
}

#documents {
    background-image: url('images/documents.jpg');
}

/* Footer styles */
footer {
    background-color: #1a1a1a;
    padding: 2rem;
    text-align: center;
    color: #888888;
    font-size: 0.9rem;
}

What are these rules doing?

Each #id selector targets one section by its HTML id and assigns it a unique background image. The shared .era class (already in your CSS) handles background-size: cover and centering, so these rules only need the image path.

The footer has a solid dark background and muted text for the attribution line.

  • Save style.css.

Seeing red squiggles in VS Code?

If VS Code shows warnings about the image paths, that is normal. The paths are correct for the web server. VS Code's linter sometimes flags relative URLs it cannot resolve locally.

Having trouble finding where to paste? Help me organise my style.css rules for the history timeline project.

✔️ Awesome, I've got everything!

Great. Confirm you have saved both index.html and style.css.

ⓧ I'd like to double check the full code

Scroll up and confirm each of these chunks is present in the correct order:

  • index.html: hero section with .overlay div, then ancient, medieval, renaissance, industrial, and documents sections (each with their own .overlay and .content divs), then the footer.
  • style.css: reset, html { scroll-behavior: smooth; }, body, .era, .overlay, .content (including h1, h2, p rules), then #hero through #documents background-image rules, then footer styles.
Redeploy and verify the full timeline

All six sections are coded and styled, and your images are ready. Time to push everything to the server.

  • Deploy your entire project folder to the server by running:
scp -r index.html style.css images/ [[SERVER_USER="your-username"]]@[[SERVER_IP="your server IP address"]]:/var/www/html/
  • SSH into your server and set the correct ownership so Nginx can read the new files:
sudo chown -R www-data:www-data /var/www/html/

Why set ownership again?

Files transferred via scp arrive owned by your user account, not www-data. Nginx runs as the www-data user and needs read access to serve these files to visitors.

Before you refresh, what do you expect to see? Will the page show all five era backgrounds, or just the hero?

  • Switch to your browser and refresh http://your server IP address.

All five historical era sections display with perfectly readable white text over darkened backgrounds. Scrolling moves you through Ancient, Medieval, Renaissance, and Industrial Revolution, and each section fills the entire screen with a different background image.

Some sections show no background image?

  • Check that the image filenames in your images/ folder match the CSS exactly (case-sensitive). For example, Ancient.jpg will not match ancient.jpg.
  • Confirm the images were copied to /var/www/html/images/ on the server by running ls /var/www/html/images/ in your SSH session.
  • Verify file ownership is set to www-data by running ls -la /var/www/html/images/.

Still stuck? Help me figure out why my background images are not showing on the deployed site.

Your timeline is live and looking sharp. Next up, you will add a PDF documents library so visitors can view and download historical files directly in the browser.

Add the PDF Documents Library

Your timeline looks incredible. Five full-screen historical eras with perfectly readable text over darkened backgrounds, all live on your server.

The site is missing something visitors would actually use: downloadable documents. In this step, you will add a PDF library where visitors can preview documents directly in the browser and download them with a single click.

In this step, get ready to:
  • Add a PDF card grid with inline document viewers to your HTML.
  • Style the cards with a translucent background and a gold download button.
  • Upload PDF files to the server and verify they load in the browser.
Add the PDF card markup to your HTML

Your documents section currently has a heading and paragraph but no actual document content. You will add a grid of PDF cards, each containing an inline viewer and a download link.

  • In VS Code, open index.html from your local project folder.
  • Find the <section class="era" id="documents"> section.
  • Inside the .content div, directly below the closing </p> tag, add this PDF grid code:
            <div class="pdf-grid">
                <div class="pdf-card">
                    <h3>Document Title One</h3>
                    <iframe src="/pdfs/document1.pdf" title="Document Title One"></iframe>
                    <a href="/pdfs/document1.pdf" download class="download-btn">Download PDF</a>
                </div>
                <div class="pdf-card">
                    <h3>Document Title Two</h3>
                    <iframe src="/pdfs/document2.pdf" title="Document Title Two"></iframe>
                    <a href="/pdfs/document2.pdf" download class="download-btn">Download PDF</a>
                </div>
            </div>

What does this code do?

  • The .pdf-grid container holds all the PDF cards and will become a responsive grid with CSS.
  • Each .pdf-card groups a title, an iframe viewer, and a download link together.
  • The <iframe src="/pdfs/document1.pdf"> embeds the PDF directly in the browser using its built-in PDF viewer.
  • The download attribute on the anchor tag tells the browser to save the file instead of navigating to it.
  • Save index.html.
  • Open a local terminal and preview the file by running:
xdg-open index.html

You should see two empty white rectangles where the iframe viewers will appear, plus "Download PDF" text links below each one. The iframes are blank because the PDF files do not exist on your local machine yet.

Not seeing the PDF card structure?

  • Make sure the pdf-grid div is inside the .content div, not outside it. It must sit between the closing </p> and the closing </div> of .content.
  • Check for any unclosed tags. Every <div> needs a matching </div>.

Why aren't my PDF cards showing up in the documents section?

✔️ Awesome, I've got everything!

Great. Double check you have saved index.html with the new pdf-grid div inside the documents section.

ⓧ I'd like to double check the full code

Your index.html is longer than 30 lines, so confirm these sections are present in order:

  • The <head> with meta tags and the stylesheet link.
  • Six <section class="era"> elements: hero, ancient, medieval, renaissance, industrial, documents.
  • Inside the documents section's .content div: the <h2>, the <p>, and the new .pdf-grid div with two .pdf-card children.
  • The <footer> at the bottom.
Style the PDF cards

The card structure exists but has no visual styling yet. You will add CSS Grid for the layout, translucent card backgrounds, and a gold download button.

  • In VS Code, open style.css.
  • At the bottom of the file, below the #documents rule and above the footer rule, add the grid layout and card container styles:
.pdf-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 2rem;
    margin-top: 2rem;
}

.pdf-card {
    background-color: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-radius: 8px;
    padding: 1.5rem;
    backdrop-filter: blur(5px);
}

.pdf-card h3 {
    font-size: 1.3rem;
    margin-bottom: 1rem;
}

What does this code do?

  • The .pdf-grid uses auto-fit with minmax(300px, 1fr) so cards wrap responsively. On wide screens you get two columns. On narrow screens, one.
  • The .pdf-card has a translucent white background with backdrop-filter: blur(5px) so the background image shows through with a frosted-glass effect.
  • Save style.css and refresh your browser preview.

You should now see two frosted-glass cards arranged side by side in the documents section. The card titles appear in white text above the iframe boxes.

Cards not styled?

  • Confirm the class name in your HTML is pdf-grid (with a hyphen). It must match .pdf-grid in your CSS exactly.
  • Make sure you pasted the CSS below the #documents rule, not inside it.

Why are my PDF cards not picking up the grid or blur styles?

  • Still in style.css, add the iframe sizing and download button styles directly below the .pdf-card h3 rule you just added:
.pdf-card iframe {
    width: 100%;
    height: 400px;
    border: none;
    border-radius: 4px;
    background-color: #ffffff;
}

.download-btn {
    display: inline-block;
    margin-top: 1rem;
    padding: 0.75rem 1.5rem;
    background-color: #d4af37;
    color: #1a1a1a;
    text-decoration: none;
    border-radius: 4px;
    font-weight: bold;
    transition: background-color 0.3s;
}

.download-btn:hover {
    background-color: #b8960c;
}

What does this code do?

  • The iframe gets a fixed height of 400px and a white background so the PDF viewer has a clear reading area inside the translucent card.
  • The .download-btn uses a gold accent colour (#d4af37) with dark text for high contrast, and darkens on hover to #b8960c.
  • Save style.css and refresh your browser preview.

You should see the iframe areas as white rectangles inside the frosted cards, with a gold "Download PDF" button below each one.

Button not showing gold?

  • Check that your HTML anchor uses class="download-btn" (not class="download-button" or similar).
  • Make sure there is no typo in #d4af37. All six hex characters matter.

Why is my download button not styled with the gold colour?

✔️ Awesome, I've got everything!

Great. Double check you have saved style.css with both chunks of new CSS below the #documents rule.

ⓧ I'd like to double check the full code

Your style.css is longer than 30 lines, so confirm these rule groups are present in order:

  • The reset (*), html, and body rules at the top.
  • The .era, .overlay, and .content rules (including h1, h2, p).
  • The six background-image ID rules (#hero through #documents).
  • The new .pdf-grid, .pdf-card, .pdf-card h3, .pdf-card iframe, .download-btn, and .download-btn:hover rules.
  • The footer rule at the very bottom.
Upload your PDFs and deploy to the server

Your HTML and CSS are ready locally. Now you need PDF files on the server so the iframe viewers have something to display.

  • Choose two PDF files you want to serve. These can be any PDFs you have (reports, articles, scanned documents).
  • In your local project folder, create a pdfs directory and copy your two PDFs into it by running:
mkdir pdfs
cp /path/to/your/first.pdf pdfs/document1.pdf
cp /path/to/your/second.pdf pdfs/document2.pdf

What names should my PDFs have?

Your HTML references /pdfs/document1.pdf and /pdfs/document2.pdf. The file names must match exactly, including lowercase letters and the number.

  • Confirm both files exist in your local pdfs/ folder by running:
ls pdfs/

You should see document1.pdf and document2.pdf listed.

  • Upload the PDF folder to your server's web root by running:
scp -r pdfs/ [[SERVER_USER="your-username"]]@[[SERVER_IP="your server's IP address"]]:/var/www/html/
  • Upload the updated index.html and style.css files to the server by running:
scp index.html style.css [[SERVER_USER="your-username"]]@[[SERVER_IP="your server's IP address"]]:/var/www/html/
  • Switch to your SSH session on the server and set the correct file ownership so Nginx can read the new files:
sudo chown -R www-data:www-data /var/www/html/

Why set ownership to www-data?

Nginx runs as the www-data user on Ubuntu. Files owned by a different user might return a 403 Forbidden error when a visitor tries to access them.

Before you check the result, what do you think will happen when you scroll to the Documents section? Will the PDFs appear inside the cards, or will you see blank space?

  • Open your browser and visit http://your server's IP address.
  • Scroll down to the Documents section.

You should see two frosted-glass cards, each with your PDF rendered inside the iframe viewer. The gold "Download PDF" button appears below each viewer.

  • Click one of the Download PDF buttons.

The PDF file downloads to your computer. Your documents library is fully working.

PDFs not loading in the iframe?

  • Verify the files are in the right location on the server by running ls /var/www/html/pdfs/ in your SSH session. You should see document1.pdf and document2.pdf.
  • Check the ownership with ls -la /var/www/html/pdfs/. The files must be owned by www-data.
  • Make sure the iframe src paths in your HTML start with a forward slash: /pdfs/document1.pdf.

My PDFs are not displaying inside the iframe viewers on my history website.

Secret mission

Add a Navigation Bar with Smooth Scrolling

With five sections plus a documents library, visitors have no way to jump between eras without scrolling through everything. Can you add a fixed navigation bar that lets them click to jump to any section?

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely on your own Ubuntu server, so there are no ongoing cloud costs.

Resources you used:

  • Nginx web server installed on your Ubuntu server.
  • Website files deployed to /var/www/html/ (index.html, style.css, images/, pdfs/).
  • Local project folder on your Desktop.
  • UFW firewall rule allowing HTTP traffic to Nginx.

Keep everything running

No action needed. Choose this if you're still actively building or want your site to remain accessible at your server's IP address.

  • Your website stays live at your server's IP address.
  • Nginx continues running and will restart automatically if the server reboots.
  • Your local project folder remains on your Desktop for future edits.

Pause - I'll come back to this later

Stop Nginx so it is no longer serving your site, but keep all files in place for when you return.

  • SSH into your server and stop Nginx by running:
sudo systemctl stop nginx
  • Disable Nginx from starting on boot by running:
sudo systemctl disable nginx

Your website files remain in /var/www/html/ and your local project folder stays on your Desktop. When you want to resume, run sudo systemctl enable nginx && sudo systemctl start nginx to bring the site back online.

Delete - I don't want to use this again

Remove all project resources from both the server and your local machine.

  • SSH into your server and remove the website files by running:
sudo rm -rf /var/www/html/index.html /var/www/html/style.css /var/www/html/images /var/www/html/pdfs
  • Remove the UFW firewall rule by running:
sudo ufw delete allow 'Nginx HTTP'
  • Uninstall Nginx completely by running:
sudo apt purge nginx nginx-common -y
sudo apt autoremove -y
  • On your local machine, delete the project folder from your Desktop by running:
rm -rf ~/Desktop/history-website
  • Confirm everything is removed by visiting your server's IP in a browser. You should see a connection error instead of your website.

Nice Work!

Nice Work!

You just built and deployed a complete multi-page history website on your own Ubuntu server. Anyone in the world can visit it by typing your server's IP address into their browser.

You've learned how to:

  • Set up Nginx on a fresh Ubuntu server and deploy a static website that anyone can access via your server's IP address.
  • Build full-viewport sections with CSS background images and solve the text-over-image readability problem using semi-transparent rgba overlays.
  • Create a PDF documents library with embedded inline viewers and download links, served efficiently by Nginx for files of any size.
  • Secret Mission: Added a fixed navigation bar with smooth-scroll anchor links so visitors can jump between historical eras instantly.

Ready to quiz yourself?