Skip to content
How-To’s & Guides

How to Point a Domain at Your Home Server Without Making a Mess

In this article

  1. Local access and public access are not the same thing
  2. Check your public IP address
  3. The dynamic IP problem
  4. The CGNAT problem
  5. Set a fixed local IP for your Ubuntu server
  6. Set up public DNS
  7. Root domain versus www
  8. Lower the TTL while testing
  9. DNS does not serve your website
  10. Set up the Apache virtual host
  11. Test Apache locally first
  12. Set up port forwarding on your router
  13. Do not forward random services
  14. Open the firewall on Ubuntu
  15. Add SSL with Let’s Encrypt
  16. Test from outside your network
  17. Check the logs when something fails
  18. A practical troubleshooting checklist
  19. Common problems and what they usually mean
  20. A safer default setup
  21. Safer alternatives to public port forwarding
  22. The simple mental model
  23. Final takeaway

Setting up an Ubuntu home server is one thing.

Getting a real domain to reach it from the public internet is another thing entirely.

This is where a lot of beginner home-server projects get confusing, because people treat “put my site online” as one job.

It isn’t.

It’s really three separate jobs that need to line up:

  1. DNS tells the internet where your domain points.
  2. Port forwarding tells your router where to send web traffic.
  3. Apache or Nginx tells your server which site to serve.

Once you understand those three pieces, the whole thing becomes much less mysterious.

The basic flow looks like this:

visitor
  → domain DNS
  → your public IP address
  → router port forward
  → Ubuntu server
  → Apache virtual host
  → website

Or, less technically:

DNS gets people to your house. Port forwarding gets them to the right room. Apache decides which website answers the door.

That’s the idea.

This guide assumes you already have an Ubuntu server running at home, Apache installed, and a basic website working locally. If your server isn’t reachable on your home network yet, fix that first.

Public DNS won’t save a local setup that doesn’t work.

Annoying, but true.


Local access and public access are not the same thing

When you visit your server from inside your house, you’re probably using an address like this:

192.168.1.50

or:

http://ubuntu-server.local

That works because you’re on the same private network as the server.

But 192.168.x.x, 10.x.x.x, and 172.16.x.x style addresses are private network addresses. They are not reachable from the public internet. You can use them inside your house, but someone outside your network can’t type 192.168.1.50 and magically land on your server.

A public domain needs to point to your router’s public IP address, not the private IP address of the Ubuntu server.

That distinction matters.

Your home network usually looks something like this:

Internet
  ↓
Your ISP
  ↓
Your router public IP
  ↓
Your home router
  ↓
Ubuntu server private IP: 192.168.1.50

Your router is the device the public internet can see.

Your Ubuntu server sits behind it.

So the job is not this:

example.com → 192.168.1.50

The job is this:

example.com → your public IP → router forwards traffic → 192.168.1.50

That’s the bit beginners often miss.

And to be fair, it is not exactly obvious until someone explains it without acting like you were born knowing router NAT rules.


Check your public IP address

Before touching DNS, you need to know your current public IP address.

The easy way is to visit a “what is my IP” site from a device connected to your home internet.

You can also run this from your Ubuntu server:

curl ifconfig.me

or:

curl https://icanhazip.com

That should return an IP address that looks something like this:

203.0.113.24

That is the address your DNS A record will point to.

At least, that’s the simple version.

There are two common problems here:

  • dynamic IP addresses
  • CGNAT

Both can make a perfectly sensible setup look broken.

Because networking enjoys comedy, apparently.


The dynamic IP problem

Many home internet connections don’t give you a permanent public IP.

Your public IP can change when your router restarts, when your ISP refreshes your connection, or just whenever your ISP decides to move things around.

If your domain points to your current IP and that IP changes later, your domain will keep pointing to the old address.

That means your site stops working.

You have a few options.

Option 1: Ask your ISP for a static IP

This is the cleanest option if your ISP offers it.

A static IP means your public IP stays the same. It’s usually a paid add-on, and not every residential ISP offers it, but it makes self-hosting much simpler.

Not exciting. Very useful.

A classic combination.

Option 2: Use dynamic DNS

Dynamic DNS updates a hostname whenever your public IP changes.

The idea is:

your home IP changes
  → dynamic DNS client notices
  → DNS record gets updated
  → domain points to the new IP

Some routers have built-in dynamic DNS support. You can also run a small updater on your Ubuntu server. Some DNS providers offer their own API-based update options.

This is a good middle ground for home servers.

Option 3: Use a tunnel instead

If you don’t want to deal with public IPs, router rules, or ISP weirdness, use something like Cloudflare Tunnel or Tailscale Funnel.

More on that later.

Cloudflare Tunnel can publish an application by mapping a public hostname to a local service, such as http://localhost:8080.

Tailscale Funnel can expose a local resource to the internet through a Funnel URL.

Both are useful options when normal port forwarding is more trouble than it’s worth.


The CGNAT problem

CGNAT is where things get annoying.

CGNAT stands for Carrier-Grade Network Address Translation.

In plain English, it means your ISP may be putting your home connection behind another layer of NAT before it reaches the public internet.

If you’re behind CGNAT, normal port forwarding may not work, because your router doesn’t actually have a true public IPv4 address.

A simple way to check is to compare:

  1. The public IP shown by a “what is my IP” website.
  2. The WAN or internet IP shown inside your router admin page.

If they don’t match, or if your router’s WAN address is in a private or shared range, you may be behind CGNAT.

This is one of those problems where you can do everything “right” and still not get inbound traffic working.

Very character-building. Also irritating.

Your options are usually:

  • ask your ISP for a real public IP or static IP
  • use IPv6 if your ISP supports it and you know what you’re doing
  • use Cloudflare Tunnel
  • use Tailscale Funnel
  • host the public version on a cheap VPS instead

For many home users, a tunnel is less painful than fighting CGNAT.


Set a fixed local IP for your Ubuntu server

Before setting up port forwarding, make sure your Ubuntu server keeps the same private IP address.

If your server is 192.168.1.50 today but becomes 192.168.1.87 next week, your router’s port forwarding rule will point to the wrong device.

The clean way to avoid this is to create a DHCP reservation in your router.

That means your router always gives the same local IP to your Ubuntu server.

For example:

Ubuntu server MAC address → always gets 192.168.1.50

You can also set a static IP directly on Ubuntu, but for most home networks I prefer a DHCP reservation in the router.

It keeps the network rules in one place and avoids accidental address conflicts.

Once that’s done, write the IP down:

Ubuntu server local IP: 192.168.1.50

You’ll need it for port forwarding.

And probably again later when something breaks and you mutter “what IP did I give that thing?” into the void.


Set up public DNS

DNS is the part that connects your domain name to your home internet connection.

For a basic home-hosted website, you usually need two records:

example.com      A      your.public.ip.address
www.example.com  CNAME  example.com

The A record points a domain name to an IPv4 address.

The CNAME record makes one hostname an alias of another hostname. For example, www.example.com can point to example.com, and then example.com points to your IP address.

In your DNS provider, it may look like this:

Type    Name    Value
A       @       203.0.113.24
CNAME   www     example.com

The @ usually means the root domain.

So:

@   = example.com
www = www.example.com

Different DNS providers have slightly different dashboards, because apparently making every interface use the same words would be too kind.

But the idea is the same.


Root domain versus www

You’ll usually want both of these to work:

example.com
www.example.com

The root domain is sometimes called the apex domain or naked domain.

The www version is just a subdomain.

For a simple home server setup, I’d usually do this:

example.com      A      your.public.ip.address
www.example.com  CNAME  example.com

Then configure Apache to accept both names.

That way visitors can type either version and still reach the site.

Later, you can decide whether you want to redirect one version to the other.

For example:

www.example.com → example.com

or:

example.com → www.example.com

Don’t worry about that yet.

First, make both work.

Then get fussy.


Lower the TTL while testing

TTL means “time to live”.

It controls how long DNS results can be cached. A longer TTL can make DNS lookups more efficient, but it also means changes may take longer to reach users.

When you’re testing, use a low TTL if your DNS provider allows it.

Something like this is fine:

300 seconds

or:

5 minutes

That way, if you make a mistake, you’re not waiting hours for the correction to show up.

Once everything works, you can raise it later.

This is one of those tiny settings that can save you a surprising amount of swearing.


DNS does not serve your website

This is worth saying clearly.

DNS does not host your site.

DNS does not configure Apache.

DNS does not open ports.

DNS does one job:

domain name → IP address

That’s it.

If your DNS is correct but your router isn’t forwarding traffic, the site still won’t work.

If your router is forwarding traffic but Apache doesn’t have the right virtual host, the wrong site may show.

If Apache is correct but your firewall blocks port 80 or 443, the browser still won’t connect.

This is why the three-part framing helps:

DNS → router → web server

Each part has a separate job.

Debug them separately, or you’ll end up clicking random settings like you’re trying to defuse a bomb in a film.


Set up the Apache virtual host

Once traffic reaches your Ubuntu server, Apache needs to know which website to serve.

Apache supports name-based virtual hosting, where multiple hostnames can be served from the same IP address.

Create a folder for your site:

sudo mkdir -p /var/www/example.com/public

Create a simple test page:

echo "<h1>example.com is working</h1>" | sudo tee /var/www/example.com/public/index.html

Set ownership.

Replace youruser with your Ubuntu username:

sudo chown -R youruser:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com

Now create the Apache config file:

sudo nano /etc/apache2/sites-available/example.com.conf

Add this:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com/public

    <Directory /var/www/example.com/public>
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
    CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>

Enable the site:

sudo a2ensite example.com.conf

You may also want to disable the default site:

sudo a2dissite 000-default.conf

Then test the Apache config:

sudo apache2ctl configtest

If it says Syntax OK, reload Apache:

sudo systemctl reload apache2

Now Apache is ready to answer requests for:

example.com
www.example.com

At least on port 80.

HTTPS comes later.

Do not start with HTTPS while basic HTTP is still broken. That way lies confusion, Certbot errors, and a strong urge to blame DNS for things DNS did not do.


Test Apache locally first

Before you blame DNS, your ISP, your router, or the moon, test Apache from inside your home network.

From another device on your network, visit:

http://192.168.1.50

If that doesn’t work, the public domain definitely won’t work yet.

You can also test from the server itself:

curl http://localhost

And if you want to test the virtual host name before DNS is fully working, use:

curl -H "Host: example.com" http://localhost

That tells Apache:

Pretend this request is for example.com.

If the correct page comes back, Apache is probably doing its job.

That’s good.

It means the problem, if there still is one, is probably further up the chain.


Set up port forwarding on your router

Now you need to tell your router what to do with incoming web traffic.

A normal website uses:

HTTP  → port 80
HTTPS → port 443

Your router receives that traffic first. Without a port forwarding rule, it doesn’t know which device inside your home should receive it.

You want rules like this:

External port 80  → Ubuntu server IP port 80
External port 443 → Ubuntu server IP port 443

Using the earlier example:

External 80  → 192.168.1.50:80
External 443 → 192.168.1.50:443

Your router may call this:

  • port forwarding
  • NAT forwarding
  • virtual server
  • port mapping
  • applications and gaming
  • firewall rules

Every router UI is slightly different, because apparently router manufacturers all got together and agreed to make this part annoying.

The important fields are usually:

Service name: HTTP
External port: 80
Internal IP: 192.168.1.50
Internal port: 80
Protocol: TCP

And:

Service name: HTTPS
External port: 443
Internal IP: 192.168.1.50
Internal port: 443
Protocol: TCP

Use TCP, not UDP, for normal web traffic.


Do not forward random services

This is the part where it’s worth slowing down.

Forwarding port 80 and 443 for a public website is normal.

Forwarding random admin tools is how you turn a fun home-server project into a bad weekend.

Do not casually expose:

  • MySQL: 3306
  • PostgreSQL: 5432
  • phpMyAdmin
  • Adminer
  • Redis
  • MongoDB
  • SSH
  • development servers
  • Docker dashboards
  • private admin panels

Could you expose some of those safely with the right setup?

Sometimes.

Should a beginner do it casually because “it works”?

No.

Keep public access boring:

80  → public HTTP
443 → public HTTPS

Everything else should stay private unless you have a very specific reason and know how you’re securing it.

If you need remote admin access, use SSH with keys and proper hardening, or use a VPN-style tool like Tailscale instead of exposing admin panels to the public internet.

The internet is full of bored machines looking for mistakes.

Don’t hand them a menu.


Open the firewall on Ubuntu

If you’re using UFW on Ubuntu, the router may forward traffic correctly but Ubuntu may still block it.

Ubuntu’s documentation describes UFW as a tool for making firewall rule management easier. In practice, it is the thing that can quietly say “no” even when everything else looks right.

To allow Apache traffic for both HTTP and HTTPS:

sudo ufw allow "Apache Full"

Check the status:

sudo ufw status

You should see something like:

Apache Full              ALLOW       Anywhere
Apache Full (v6)         ALLOW       Anywhere (v6)

If UFW is not enabled yet, be careful before enabling it over SSH.

Make sure SSH is allowed first:

sudo ufw allow OpenSSH
sudo ufw allow "Apache Full"
sudo ufw enable

Then check:

sudo ufw status verbose

Again: don’t lock yourself out of your own server.

That’s a classic server-admin rite of passage, but you don’t need to speedrun it.


Add SSL with Let’s Encrypt

Once your domain reaches your server over HTTP, you can add HTTPS.

Let’s Encrypt is a free certificate authority, and Certbot is the common tool used to obtain and install Let’s Encrypt certificates.

Install Certbot and the Apache plugin:

sudo apt update
sudo apt install certbot python3-certbot-apache

Then request a certificate:

sudo certbot --apache -d example.com -d www.example.com

Certbot will try to verify that you control the domain.

For the standard HTTP validation flow, your domain usually needs to reach your server on port 80.

That means these must already work:

  • DNS points to your public IP
  • router forwards port 80
  • Ubuntu firewall allows port 80
  • Apache serves the site

If any of those are broken, SSL setup will probably fail.

After Certbot succeeds, it will update your Apache configuration and set up HTTPS. It may also offer to redirect HTTP to HTTPS.

Test renewal:

sudo certbot renew --dry-run

That checks whether automatic renewal is likely to work when the certificate needs refreshing.

The practical rule is simple:

Get HTTP working first. Then add HTTPS.

It is much easier to debug one problem at a time.


Test from outside your network

This is important.

Don’t only test from your home Wi-Fi.

Some routers support NAT loopback, which means your domain may work from inside your network even if it doesn’t work properly from outside.

Other routers don’t support it, which means your domain may fail inside the network but work fine outside.

Helpful, obviously.

The clean test is:

  1. Turn off Wi-Fi on your phone.
  2. Use mobile data.
  3. Visit http://example.com.
  4. Visit https://example.com.
  5. Test the www versions too.

Test all of these:

http://example.com
https://example.com
http://www.example.com
https://www.example.com

If it works on mobile data, you’ve proven the public path works.

You can also use DNS checker tools to confirm your domain is resolving to the expected public IP from different locations.


Check the logs when something fails

Logs are boring until they save you an hour.

For Apache access logs:

sudo tail -f /var/log/apache2/example.com-access.log

For Apache error logs:

sudo tail -f /var/log/apache2/example.com-error.log

For general Apache logs:

sudo tail -f /var/log/apache2/access.log
sudo tail -f /var/log/apache2/error.log

Now try loading the site from outside your network.

If nothing appears in the logs, the request probably isn’t reaching Apache.

That points to:

  • DNS
  • public IP
  • router forwarding
  • ISP blocking
  • CGNAT
  • Ubuntu firewall

If the request appears in the logs but the wrong site loads, the issue is probably Apache virtual host configuration.

If the request appears and returns an error, Apache is receiving the traffic and your problem is likely inside the site config, permissions, app code, or document root.

That’s good news, weirdly.

It means the network path is working.

You have moved from “internet mystery” to “server problem”, which is not fun exactly, but it is at least a smaller sort of pain.


A practical troubleshooting checklist

When your domain doesn’t work, don’t randomly change five things at once.

Work through the path in order.

1. Does the site work locally on the server?

Run:

curl http://localhost

If this fails, fix Apache before touching DNS or the router.

2. Does the site work from another device at home?

Visit:

http://192.168.1.50

If this fails, check Apache, UFW, and your server’s local IP.

3. Does Apache recognise the domain?

Run:

curl -H "Host: example.com" http://localhost

If this returns the wrong site, check your virtual host.

4. Does DNS point to your public IP?

Check your DNS provider.

You want:

example.com → your public IP

Not:

example.com → 192.168.1.50

Never point public DNS at your private LAN address.

5. Is the router forwarding ports 80 and 443?

Check that the rules point to the right local IP.

80  → 192.168.1.50:80
443 → 192.168.1.50:443

If your server’s local IP changed, fix that with a DHCP reservation.

6. Is UFW allowing Apache?

Run:

sudo ufw status

Make sure Apache Full or equivalent port rules are allowed.

7. Are you behind CGNAT?

Compare your router WAN IP with your public IP from a “what is my IP” site.

If they don’t match, this may be the problem.

8. Is your ISP blocking inbound ports?

Some ISPs block inbound port 80, especially on residential connections.

If port 443 works but port 80 doesn’t, or neither works despite correct configuration, check your ISP’s policy.

9. Has DNS actually updated?

DNS changes can take time depending on TTL, caching, and provider behaviour.

Lower TTL helps while testing, but cached results can still linger for a while.

10. Is SSL failing because HTTP is broken?

Certbot’s normal Apache flow expects the site to already be reachable.

If http://example.com doesn’t reach your server, don’t start with Certbot.

Fix plain HTTP first.

Boring order. Better outcome.


Common problems and what they usually mean

The domain points to 192.168.x.x

This is wrong for public DNS.

Your DNS should point to your public IP, not your Ubuntu server’s private IP.

The router forwards to the wrong device

This happens when the server’s local IP changes.

Set a DHCP reservation.

Apache shows the default page

Apache is receiving traffic, but your virtual host probably isn’t enabled or isn’t matching the domain.

Check:

sudo apache2ctl -S

That shows which virtual hosts Apache knows about.

Certbot fails validation

Usually this means the domain does not currently reach your server over port 80.

Check DNS, port forwarding, UFW, and Apache before retrying.

The site works at home but not outside

Possible causes include:

  • router NAT loopback confusion
  • port forwarding not working
  • ISP blocks inbound traffic
  • CGNAT
  • firewall issue

Test from mobile data.

The site works outside but not inside

Your router may not support NAT loopback.

You can either live with it, use local DNS, edit your local hosts file for testing, or access the server locally by its private IP when you’re at home.

HTTPS works but HTTP doesn’t

You may have a redirect or firewall rule issue.

Also check whether port 80 is open, because Certbot renewals using HTTP validation may need it.

The wrong site loads

Apache virtual host matching is probably off.

Check:

sudo apache2ctl -S

Make sure ServerName and ServerAlias match the domain you’re visiting.


A safer default setup

For a basic public home-hosted website, I’d keep the public surface area small.

Public:

  • 80 HTTP
  • 443 HTTPS

Private:

  • SSH
  • database
  • admin panels
  • dev tools
  • dashboards

Then I’d use:

  • SSH keys, not password login
  • UFW enabled
  • automatic security updates
  • regular backups
  • DHCP reservation for the server
  • simple Apache virtual hosts
  • Let’s Encrypt for HTTPS
  • a tunnel or VPN for private tools

The goal is not to make your home server “enterprise grade”.

The goal is to avoid silly mistakes.

Silly mistakes are very committed to becoming serious mistakes if you give them enough internet exposure.


Safer alternatives to public port forwarding

Opening your home server to the internet can be useful, but it isn’t always the best option.

Here are the alternatives I’d consider.

Cloudflare Tunnel

Cloudflare Tunnel lets you expose an app through Cloudflare without opening inbound ports on your router.

Instead of the internet connecting directly to your home IP, a local cloudflared service connects out to Cloudflare, and Cloudflare routes the hostname to your local service.

Cloudflare’s current docs describe this as adding a public hostname route that maps a hostname, such as app.example.com, to a local service, such as http://localhost:8080.

This is useful if:

  • your ISP uses CGNAT
  • you don’t want to open router ports
  • you already use Cloudflare for DNS
  • you want a simpler public hostname setup

It’s not magic, and you still need to understand what you’re exposing, but it removes a lot of router pain.

Tailscale Funnel

Tailscale Funnel can expose a local service to the broader internet from a device in your Tailscale network.

Tailscale’s docs describe Funnel as exposing a local resource to the internet through a Funnel URL.

This is useful for:

  • temporary demos
  • sharing a small local service
  • testing webhooks
  • avoiding router configuration

Tailscale also has private sharing options for things you don’t want fully public.

For private admin tools, that is usually the more sensible direction.

Cheap VPS

A small VPS is often the boring grown-up answer.

For a few dollars a month, you get:

  • a real public IP
  • cleaner DNS setup
  • no home router issues
  • no CGNAT problem
  • better uptime
  • easier separation from your home network

You can still keep your home server for development, backups, private apps, and experiments.

Then use the VPS for public-facing projects.

This is less fun than making the home server do everything, but sometimes boring wins because boring sleeps better.

Private VPN access only

Not every home server app needs to be public.

For personal dashboards, admin tools, databases, notes, internal apps, and experiments, private VPN access is often better.

Use something like:

  • Tailscale
  • WireGuard
  • ZeroTier

Then only your own devices can reach the service.

That’s usually the right answer for admin tools.

If the public does not need it, do not make it public just because you can.


The simple mental model

Here’s the whole setup again:

visitor types example.com
  ↓
DNS resolves example.com to your public IP
  ↓
visitor's browser connects to your router
  ↓
router forwards port 80/443 to your Ubuntu server
  ↓
UFW allows the traffic
  ↓
Apache receives the request
  ↓
Apache matches ServerName or ServerAlias
  ↓
Apache serves files from DocumentRoot

When something breaks, don’t debug it as one giant mystery.

Debug it as a chain.

Ask:

  • Did DNS send the visitor to the right public IP?
  • Did the router forward the traffic to the right local server?
  • Did Ubuntu allow the traffic through the firewall?
  • Did Apache match the domain to the right site?
  • Did the site itself load correctly?

That’s the workflow.

Not glamorous.

Still better than rage-clicking router settings at 1am while developing a personal grudge against your ISP.


Final takeaway

Pointing a domain at a home server feels confusing because several separate systems have to agree with each other.

DNS has to point to your public IP.

Your router has to forward web traffic to your Ubuntu server.

Ubuntu has to allow the traffic.

Apache has to know which site to serve.

Let’s Encrypt has to be able to reach the domain before it can issue a certificate.

Once you split those into separate jobs, the setup becomes much easier to reason about.

The memorable version:

DNS gets people to your house. Port forwarding gets them to the right room. Apache decides which website answers the door.

That’s the whole guide in one line.

↑ Top