Home Media Server Setup#
Introduction#
Last modified: Oct 11, 2025
If you have some media (music, ebooks, audiobooks, photos, movies, home videos, etc.) that you want to easily view on your phone, your TV, at home and away from home, then this guide is for you. I’ll walk you through the best and easiest way I’ve found to do this using open source software.
The Server#
Hardware#
You need a machine with a halfway decent processor and 8 GB of RAM. You will need a lot of storage depending on how much media you have. A full 4k movie is around 50 or 60 GB, for example.
Basic Software#
Just about any linux distribution will do. If the linux distribution has a server install that skips all the desktop GUI packages then that’s ideal. Make sure it uses lvm partitions (they are easy to grow and add more disks to down the road) when partitioning your disk(s).
You will also need these packages:
openssh server
docker (not docker desktop, I don’t know what that is)
docker-compose
Caddy
I run archlinux, and if you want something that’s a little easier to get started with you’ll probably go with Ubuntu. Everything else should be a simple:
sudo apt install openssh docker docker-compose caddy
sudo systemctl enable --now ssh docker caddy
but the package names and service names could vary a little from that.
You don’t technically need rootless docker, but I like not needing to type sudo for everything. There are instructions for setting up rootless docker here that look like they’d work with Ubuntu.
Files and Directories#
This is the directory structure you should use for your media. Don’t worry about moving or copying your files there yet, but create the directories somewhere on your big disk. These folder should all be on the same disk partition. The top level directory can be named whatever you want, but the rest should match this
media-lib-home/
├── media
│ ├── books
│ │ ├── Audiobooks
│ │ └── Books
│ ├── movies
│ ├── music
│ ├── podcasts
│ └── tv
├── torrents
│ ├── books
│ ├── movies
│ ├── music
│ ├── software
│ └── tv
└── usenet
├── complete
│ ├── audiobooks
│ ├── books
│ ├── misc
│ ├── movies
│ ├── music
│ └── tv
└── incomplete
Here’s a little script to create that structure:
mkdir -p media-lib-home/media/books/Audiobooks
mkdir -p media-lib-home/media/books/Books
mkdir -p media-lib-home/media/movies
mkdir -p media-lib-home/media/music
mkdir -p media-lib-home/media/podcasts
mkdir -p media-lib-home/media/tv
mkdir -p media-lib-home/media/torrents/books
mkdir -p media-lib-home/media/torrents/movies
mkdir -p media-lib-home/media/torrents/music
mkdir -p media-lib-home/media/torrents/software
mkdir -p media-lib-home/media/torrents/tv
mkdir -p media-lib-home/media/usenet/complete/audiobooks
mkdir -p media-lib-home/media/usenet/complete/books
mkdir -p media-lib-home/media/usenet/complete/misc
mkdir -p media-lib-home/media/usenet/complete/movies
mkdir -p media-lib-home/media/usenet/complete/music
mkdir -p media-lib-home/media/usenet/complete/tv
mkdir -p media-lib-home/media/usenet/incomplete
If you have other categories like home videos or other you can add those directories under media with books, movies, etc.
If you know you are not going to use bittorrent or Usenet to obtain any media, then you can skip creating those directories.
You’ll want some other directory for storing configuration files for the various software that will be downloading, organizing, and serving your media. That can be on another partion if you want, it doesn’t take much disk space at all. I made a directory named web in my home directory for this and that’s what I’ll use in my examples below.
Networking, DNS, etc.#
If you are only going to access your media on your home network, then you might want to set up DNS on your home network and give your server a domain name like media.localdomain or something. Or just use the IP address everwhere it’s needed.
If you don’t have a static IP address, there is a section for Dynamic DNS a little lower down on the page. It has a software download link that will help you get that all set up.
If you want to access your media from anywhere, then you need to purchase a domain name and configure the DNS. Ideally you have a static IP address for your home internet, but if you don’t it’s not the end of the world.
I recommend namecheap for purchasing your domain name and their Namecheap BasicDNS is all you need to go along with that. Once you buy your domain name, click on Advanced DNS and add an A Record, or if you don’t have a static IP address add an A +Dynamic DNS Record. Actually, add two of those. For the host enter * for the first and @ for the second. Enter your home IP address, and leave the TTL as automatic.
For the rest of this build I’ll use example.com as your domain name, but you substitute yours (e.g., radical-stuff.com, or foobar.xyz, or whatever) for that wherever you see it.
For access from anywhere you will also need to set up port forwarding (assuming your machine is behind a NAT, which is very very likely is). Forward ports 80 and 443 to this server. You can also forward 22 if you want to be able to ssh to it from anywhere.
Media Type Specific Software#
Movies, TV, and Possibly More#
You will want to use Jellyfin for video content. It can also do music, ebooks, audiobooks, and photos, but there are better apps for those you might want to try.
There are jellyfin apps for android and ios, for Roku and google TV. On a computer or a phone just using your web browser works well too. Jellyfin is very well done.
To set it up, first make some directories:
mkdir -p ~/web/jellyfin/cache
mkdir -p ~/web/jellyfin/config
Then in the ~/web/jellyfin directory create a file named docker-compose.yaml and put this in the file:
services:
jellyfin:
image: jellyfin/jellyfin
container_name: jellyfin
network_mode: 'host'
volumes:
- ./config:/config
- ./cache:/cache
- type: bind
source: /path/to/media-lib-home/media
target: /media
restart: 'unless-stopped'
# Optional - alternative address used for autodiscovery
environment:
- JELLYFIN_PublishedServerUrl=https://jellyfin.example.com
# Optional - may be necessary for docker healthcheck to pass if running in host network mode
extra_hosts:
- 'host.docker.internal:host-gateway'
Note that you need to change the source line to have the path to your media library and then the media directory at the end.
You also need to change the JELLYFIN_PublishedServerUrl to match the domain you set up above. You don’t have to prefix it with jellyfin, you can use media or whatever word you want.
Save that file and then in that directory run:
docker compose up -d
You’ll see it download stuff and hopefully no errors.
Next, edit /etc/caddy/Caddyfile and add this entry to it:
jellyfin.example.com {
reverse_proxy http://localhost:8096
}
Then type:
sudo systemctl reload caddy
And now you can go to the URL you specified in JELLYFIN_PublishedServerUrl and see Jellyfin running.
Log in and add “libraries” to Jellyfin. You can add a movies library and point it to the movies folder, a books library and point it to your books folder, etc. If you want to manually add media to your Jellyfin libraries, follow its folder structure guides. If you plan to download movies and TV series from Usenet, the tools we’ll set up for that will automatically organize the files in a way that Jellyfin likes.
Music#
coming soon: beets and jellyfin, or maybe there’s something better?
Audio Books#
coming soon: audiobookshelf and beets
eBooks#
coming soon: calibre-web-automated
Photos#
coming soon: immich
Obtaining Media#
Usenet#
You will need accounts with both of these providers, one for search (indexing) and one for actual downloading:
Create those accounts (yes, they cost money) before proceeding.
Usenet Downloader: sabnzbd#
For downloading media from Usenet we’ll use a program called sabnzbd. We’ll put all the Usenet software in the same folder and same docker-compose.yaml file, because it never really makes sense to run one of these by itself.
If you haven’t already, Create a directory for all this Usenet stuff, such as ~/web/usenet. Then add these sub-directories for sabnzbd:
mkdir -p ~/web/usenet/config/sabnzbd
Then create/edit a docker-compose.yaml file in that directory and put this in it:
name: usenet
services:
sabnzbd:
container_name: sabnzbd
image: ghcr.io/hotio/sabnzbd:latest
restart: always
logging:
driver: json-file
ports:
- 8080:8080
- 9090:9090
environment:
- PUID=1000
- PGID=1000
volumes:
- /etc/localtime:/etc/localtime:ro
- ./config/sabnzbd:/config
- /path/to/media-lib-home/usenet:/data/usenet:rw
Note that you need to change the line under volumes to have the path to your media library and then the usenet directory at the end.
Save that file and then in that directory run:
docker compose up -d
You’ll see it download stuff and hopefully no errors.
There’s really no reason to access this from the internet, so we won’t set up web access to it with Caddy.
On your local network you can browse to it by going to http://server-ip-address:8080. Click the gear icon and then click to the Servers tab. Click + Add Server and enter the information from your newshosting account. Log on to your newshosting account and the dashboard has all the information you need:
server address
port (use the SSL port number)
username (it’s just random numbers and letters)
password
number of connections
The other fields you can leave at the default settings. Then click test server and if it works save it.
sabnzbd isn’t very interesting on its own, so proceed to the next one, prowlarr.
Usenet Search: prowlarr#
For searching Usenet and initiating downloads from Usenet we’ll use a program called prowlarr. It will use sabnzbd under the hood, so make sure you have that set up first. We’ll put all the Usenet software in the same folder and same docker-compose.yaml file, because it never really makes sense to run one of these by itself.
If you haven’t already, Create a directory for all this Usenet stuff, such as ~/web/usenet. Then add these sub-directories for prowlarr:
mkdir -p ~/web/usenet/config/prowlarr
Then edit the docker-compose.yaml file in that directory and add this to it under the sabnzbd stuff (NOTE: this should all be indented two spaces, the same as the sabnzbd section):
prowlarr:
container_name: prowlarr
image: ghcr.io/hotio/prowlarr:latest
restart: always
logging:
driver: json-file
ports:
- 9696:9696
environment:
- PUID=1000
- PGID=1000
volumes:
- /etc/localtime:/etc/localtime:ro
- ./config/prowlarr:/config
Save that file and then in that directory run:
docker compose up -d
You’ll see it download stuff and hopefully no errors.
Next, edit /etc/caddy/Caddyfile and add this entry to it:
prowlarr.example.com {
reverse_proxy http://localhost:9696
}
Then type:
sudo systemctl reload caddy
And now you can go to https://prowlarr.example.com (substitute your domain name) and get started. Once you are in, click Add Indexer, then in the top text box start typing nzbgeek and choose that from the search results. On the nzbgeek website click on my account to get your URL and API key (you might need to click to generate the key the first time). Once you enter the info into prowlarr click test and if it’s working click save.
Next click settings, download clients, then the big + button. Click sabnzbd, then put localhost for the host and port 8080. Back in the sabnzbd interface, click on Settings, General and then in the Security section you can get your API key. Give it the default category “misc” (matching the folder name we use in our media-lib-home/usenet/complete directories). Hit test, and then if it works, save.
Now, you should be able to click on search and search for something and download it. But, if you are going to download movies or TV shows, there are better tools. Please proceed.
Usenet Movie Search: radarr#
For searching Usenet for movies and initiating downloads from Usenet we’ll use a program called radarr. It will use prowlarr under the hood, so make sure you have that set up first. We’ll put all the Usenet software in the same folder and same docker-compose.yaml file, because it never really makes sense to run one of these by itself.
If you haven’t already, Create a directory for all this Usenet stuff, such as ~/web/usenet. Then add these sub-directories for radarr:
mkdir -p ~/web/usenet/config/radarr
Then edit the docker-compose.yaml file in that directory and add this to it under the sabnzbd stuff (NOTE: this should all be indented two spaces, the same as the sabnzbd section):
radarr:
container_name: radarr
image: ghcr.io/hotio/radarr:latest
restart: always
logging:
driver: json-file
ports:
- 7878:7878
environment:
- PUID=1000
- PGID=1000
volumes:
- /etc/localtime:/etc/localtime:ro
- ./config/radarr:/config
- /path/to/media-lib-home/:/data
Note that you need to change the line under volumes to have the path to your media library.
Save that file and then in that directory run:
docker compose up -d
You’ll see it download stuff and hopefully no errors.
Next, edit /etc/caddy/Caddyfile and add this entry to it:
radarr.example.com {
reverse_proxy http://localhost:7878
}
Then type:
sudo systemctl reload caddy
And now you can go to https://radarr.example.com (substitute your domain name) and get started.
Once you log into Radarr, go to settings, general, and generate an API key. It says you need to restart after generating the key, so you might need to go to the command line on your server and run these commands:
docker compose down
docker compose up -d
Then go to prowlarr, click settings, apps, and add a new app. Add radarr. Make the sync level full sync. For the prowlarr url use http://localhost:9696, and for the radarr url use http://localhost:7878. Put in the API key from radarr, then click test. If that works click save. My memory is a little fuzzy now, but I think that’s all you have to do and now you can use radarr to search for movies and download them. Once it has fully downloaded a movie it will magically show up in jellyfin.
Usenet TV Series Search: sonarr#
For searching Usenet for movies and initiating downloads from Usenet we’ll use a program called sonarr. It will use prowlarr under the hood, so make sure you have that set up first. We’ll put all the Usenet software in the same folder and same docker-compose.yaml file, because it never really makes sense to run one of these by itself.
If you haven’t already, Create a directory for all this Usenet stuff, such as ~/web/usenet. Then add these sub-directories for sonarr:
mkdir -p ~/web/usenet/config/sonarr
Then edit the docker-compose.yaml file in that directory and add this to it under the sabnzbd stuff (NOTE: this should all be indented two spaces, the same as the sabnzbd section):
sonarr:
container_name: sonarr
image: ghcr.io/hotio/sonarr:latest
restart: always
logging:
driver: json-file
ports:
- 8989:8989
environment:
- PUID=1000
- PGID=1000
volumes:
- /etc/localtime:/etc/localtime:ro
- ./config/sonarr:/config
- /path/to/media-lib-home/:/data
Note that you need to change the line under volumes to have the path to your media library.
Save that file and then in that directory run:
docker compose up -d
You’ll see it download stuff and hopefully no errors.
Next, edit /etc/caddy/Caddyfile and add this entry to it:
sonarr.example.com {
reverse_proxy http://localhost:8989
}
Then type:
sudo systemctl reload caddy
And now you can go to https://sonarr.example.com (substitute your domain name) and get started.
Once you log into Sonarr, go to settings, general, and generate an API key. It says you need to restart after generating the key, so you might need to go to the command line on your server and run these commands:
docker compose down
docker compose up -d
Then go to prowlarr, click settings, apps, and add a new app. Add sonarr. Make the sync level full sync. For the prowlarr url use http://localhost:9696, and for the sonarr url use http://localhost:8989. Put in the API key from sonarr, then click test. If that works click save. My memory is a little fuzzy now, but I think that’s all you have to do and now you can use sonarr to search for tv shows and download them. Once it has fully downloaded a show it will magically show up in jellyfin.