Cómo hice mi propio mirror de Arch Linux
Hace un año y medio que uso Arch Linux como daily driver y me di cuenta que cada vez que actualizaba mi sistema las descargas eran lentas y tardaban bastante, investigando observé que no había ningún mirror en mi país (Argentina). Así que decidí, ya teniendo la infra necesaria para exponer un servicio a internet, crear uno propio.
En base a la wik de Arch y el post de Sindastra, creé mi propio Mirror. Actualmente es el único de Argentina y bastante usado en la región. Estas son las estadísticas del dia de ayer, la cuales se actualizan diariamente en base a los logs de Nginx:Prerrequisitos
Para poder usar este post como guia, debes tener las siguientes cosas:
Crear el directorio
Lo primero es crear el directorio donde van a vivir los archivos del mirror en el servidor:
mkdir -p /var/www/mirrors/archlinux/
Elegir un mirror Tier 1
Como puede observarse en la wiki de Arch Linux, los mirrors se manejan por tiers, cuando se empieza vamos a ser un tier 2 que es de donde la gente actualiza sus sistemas. A su vez nosotros deberemos sincronizar nuestro mirror desde un tier 1, ya que para facilitar las sincronizaciónes Arch pide que no sincronices directamente desde sus servidores. Debés elegir uno Tier 1 de esta lista:
https://archlinux.org/mirrors/tier/1/
Deberás tener en cuenta varios factores, como cercanía geografica, velocidad y uptime del Tier 1
Primera sincronización
La primera sync tarda bastante (en mi caso como 8hs) y ocupa aproximadamente 200 GiB. Recomiendo correrlo en tmux o usar un script junto a un cron para que lo haga en segundo plano. Arch solicita que usemos flags de rsync para realizar la sincronización, el comando completo sería el siguiente:
rsync -rlptH --safe-links --delete-delay --delay-updates rsync://TU_MIRROR_TIER1/archlinux/ /var/www/mirrors/archlinux/
Sincronizaciones
Yo decidí desarrollar un script (con la ayuda de Claude), para realizar las sincronizaciónes.
#!/usr/bin/env bash
# archmirror-sync.sh Sincroniza el mirror de Arch Linux con log completo.
DEST="/var/www/mirrors/archlinux"
SOURCE="rsync://archlinux.c3sl.ufpr.br/archlinux/"
LOG_DIR="/var/log/archmirror"
LOG_FILE="$LOG_DIR/sync.log"
LOCK_FILE="/var/run/archmirror-sync.lock"
MAX_LOG_MB=50 # rotar log si supera este tamaño en MB
RSYNC_TIMEOUT=2700 # timeout máximo de la operación en segundos. En mi primer syn puse 10hs
log() {
local level="$1"; shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE"
}
rotate_log() {
if [[ -f "$LOG_FILE" ]]; then
local size_mb
size_mb=$(du -m "$LOG_FILE" | cut -f1)
if (( size_mb >= MAX_LOG_MB )); then
mv "$LOG_FILE" "${LOG_FILE}.$(date '+%Y%m%d_%H%M%S').old"
log "INFO" "Log rotado por tamaño (${size_mb}MB >= ${MAX_LOG_MB}MB)"
fi
fi
}
cleanup() {
rm -f "$LOCK_FILE"
}
mkdir -p "$LOG_DIR"
rotate_log
if [[ -f "$LOCK_FILE" ]]; then
old_pid=$(cat "$LOCK_FILE")
if kill -0 "$old_pid" 2>/dev/null; then
log "WARN" "Sync ya en ejecución (PID $old_pid). Abortando."
exit 1
else
log "WARN" "Lock obsoleto encontrado (PID $old_pid ya no existe). Limpiando."
rm -f "$LOCK_FILE"
fi
fi
echo $$ > "$LOCK_FILE"
trap cleanup EXIT
log "INFO" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log "INFO" "Iniciando sync | PID=$$"
log "INFO" "Fuente: $SOURCE"
log "INFO" "Destino: $DEST"
START_TIME=$(date +%s)
timeout "$RSYNC_TIMEOUT" rsync \
--recursive \
--links \
--perms \
--times \
--hard-links \
--safe-links \
--delete-delay \
--delay-updates \
--timeout=120 \
--contimeout=30 \
--info=progress2 \
--log-file="$LOG_FILE" \
--log-file-format="[%t] [RSYNC] %o %f %l bytes" \
"$SOURCE" "$DEST" 2>&1 | while IFS= read -r line; do
# Filtrar líneas vacías y de progreso puro (no las guardamos en el log)
[[ -z "$line" || "$line" =~ ^[[:space:]]+[0-9] ]] && continue
log "RSYNC" "$line"
done
RSYNC_EXIT="${PIPESTATUS[0]}"
END_TIME=$(date +%s)
ELAPSED=$(( END_TIME - START_TIME ))
ELAPSED_FMT=$(printf '%02d:%02d:%02d' $((ELAPSED/3600)) $((ELAPSED%3600/60)) $((ELAPSED%60)))
case "$RSYNC_EXIT" in
0)
log "INFO" "Sync completado OK en $ELAPSED_FMT"
;;
24)
# Código 24 = archivos desaparecieron durante la transferencia (normal)
log "WARN" "Sync completado con advertencias (código 24 — archivos modificados durante sync) en $ELAPSED_FMT"
;;
28)
log "ERROR" "Sync interrumpido por timeout ($RSYNC_TIMEOUT segundos)"
;;
*)
log "ERROR" "Sync falló con código de salida $RSYNC_EXIT en $ELAPSED_FMT"
;;
esac
# Tamaño del mirror
if [[ -d "$DEST" ]]; then
MIRROR_SIZE=$(du -sh "$DEST" 2>/dev/null | cut -f1)
log "INFO" "Tamaño del mirror: $MIRROR_SIZE"
fi
log "INFO" "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit "$RSYNC_EXIT"
Sync automático con cron
Cabe destacar que arch solicita que sincronicemos al menos una vez al día, pero no más de una vez por hora, estas nuevas sincronizaciónes tardarán MUCHO menos que la primera (entre 8 y 25 mins). En mi caso lo hago una vez por hora. A su vez, debido a que estamos generando logs habrá que eliminarlos cada X tiempo para que no nos llene el File System. Esta es mi configuración de Cron:
#Sincronizo los repos
21 */1 * * * /bin/bash /root/archmirror-sync.sh
#Elimino logs viejos:
45 1 * * 0 find /var/log/archmirror -name "*.old" -mtime +30 -delete
Configurar nginx
Para poder exponer el mirror instalaremos los siguientes paquetes:
sudo apt install nginx libnginx-mod-http-fancyindex
Luego deberemos configurar nginx editando el archivo /etc/nginx/sites-available/tu-web, y pondremos la siguiente configuración:
#Archlinux
server {
listen 80;
listen [::]:80;
server_name archlinux.{TUDOMINIO.COM};
root /var/www/mirrors/archlinux;
#Oculta la versión de Nginx actual (por seguridad)
server_tokens off;
#OPCIONAL: Si Queres personalizar fancy Index, crea esas rutas y modifica el headery footer:
#location = /header.html {
# root /var/www/mirrors/personalizar;
#}
#location = /footer.html {
# root /var/www/mirrors/personalizar;
#}
#Unicamente permito metodos GET y HEAD
location / {
limit_except GET HEAD {
deny all;
}
try_files $uri $uri/ =404;
fancyindex on;
fancyindex_exact_size off;
fancyindex_localtime on;
fancyindex_directories_first on;
#opcionales - solo si se personalizo
#fancyindex_header "/header.html";
#fancyindex_footer "/footer.html";
}
}
Como exponerlo a Internet - SelfHosting
Acá tendremos que tomar una decisión. Queremos exponerlo usando HTTP, HTTPS o ambas?
Para esta seccion, usaremos un reverse proxy (NPM en mi caso), lo que debemos hacer es hacer un port forwarding de puertos en nuestro router domestico hacia la direccion IP de NPM. En base a la decisión que tomamos anteriormente, hacemos un port forwarding del puerto 80 (HTTP), 443 (HTTPS) o ambos. Luego en NPM manejamos otra redireccion hacia el servidor que aloja el mirror.
Yo lo manejo de esta forma porque me permite redirigir el trafico a un servidor o a otro dependiendo de la URL que se consulto. Ademas, NPM me prmite forzar SSL/TLS.
Registro oficial en archlinux.org
Una vez que tengamos todo listo y funcionando - y en caso de que queramos - podemos oficializar el mirror para que aparezca en la lista de mirrors oficiales de Arch. Para esto seguimos los pasos detallados en la wiki, primero tenemos que crearon una cuenta en el gitlab de Arch y esperar que nos las aprueben, después debemos crear un nuevo issue y poner los datos que no solicitan en el punto 4.4 de la wiki; https://wiki.archlinux.org/title/DeveloperWiki:NewMirrors
Mejora Continua: Monitoreo con alertas de Discord
Hace unos días el mirror estaba presentando bajas velocidades de descarga, estamos hablando del rango de los Kbs (KiloBytes x Segundo), lo que generaba timeouts y que mi mirror no pueda ser utilizado.
Posterior a arreglar el issue, que para el que le interese era un problema sobre como pfsense (mi router) maneja las sesiones, procedí a crear un script que se encarga de verificar que los archivos del mirror se puedan descargar y que este esté actualizado, en caso de que no, genera alertas por Discord.
#!/bin/bash
MIRROR="https://archlinux.juancho.com.ar"
DISCORD_WEBHOOK="{}"
TMPDIR=$(mktemp -d)
STATE_FILE="/var/tmp/mirror-state"
LOG_FILE="/var/log/archmirror/mirror-check.log"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG_FILE"
}
notify() {
curl -s -H "Content-Type: application/json" \
-d "{\"embeds\": [{
\"title\": \"$1\",
\"description\": \"$2\",
\"color\": $3,
\"footer\": {\"text\": \"$(date)\"}
}]}" \
"$DISCORD_WEBHOOK"
log "NOTIFY: $1 - $2"
}
mkdir -p /var/log/archmirror
PREV_STATE=$(cat $STATE_FILE 2>/dev/null || echo "ok")
CURRENT_STATE="ok"
log "=== Iniciando chequeo ==="
# 1. HTTP check
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$MIRROR/lastsync")
log "HTTP check: $HTTP_CODE"
if [ "$HTTP_CODE" != "200" ]; then
notify "❌ Mirror caído" "HTTP $HTTP_CODE en \`$MIRROR\`" 15158332
echo "down" > $STATE_FILE
rm -rf $TMPDIR; exit 1
fi
# 2. Lastsync viejo
LASTSYNC=$(curl -s "$MIRROR/lastsync")
NOW=$(date +%s)
AGE=$((NOW - LASTSYNC))
log "Lastsync age: ${AGE}s ($((AGE/3600))h)"
if [ $AGE -gt 7200 ]; then
notify "⚠️ Mirror desactualizado" "Última sync hace **$((AGE/3600))h**" 16776960
CURRENT_STATE="warn"
fi
# 3. Descargar y verificar core.db
log "Descargando core.db..."
curl -s --max-time 30 -o "$TMPDIR/core.db" "$MIRROR/core/os/x86_64/core.db"
if ! file "$TMPDIR/core.db" | grep -q "gzip"; then
notify "❌ core.db corrupto" "El archivo descargado no es un gzip válido" 15158332
log "ERROR: core.db no es gzip válido"
CURRENT_STATE="down"
rm -rf $TMPDIR; exit 1
fi
if ! tar -tzf "$TMPDIR/core.db" > /dev/null 2>&1; then
notify "❌ core.db no extraíble" "El archivo está corrupto o incompleto" 15158332
log "ERROR: core.db no se puede extraer"
CURRENT_STATE="down"
rm -rf $TMPDIR; exit 1
fi
log "core.db OK"
# 4. Comparar con upstream
UPSTREAM_SYNC=$(curl -s "https://geo.mirror.pkgbuild.com/lastsync")
DIFF=$((UPSTREAM_SYNC - LASTSYNC))
log "Diff con upstream: ${DIFF}s ($((DIFF/3600))h)"
if [ $DIFF -gt 7200 ]; then
notify "⚠️ Mirror atrasado" "**$((DIFF/3600))h** detrás del upstream" 16776960
CURRENT_STATE="warn"
fi
# 5. Notificar recuperación
if [ "$PREV_STATE" = "down" ] && [ "$CURRENT_STATE" = "ok" ]; then
notify "✅ Mirror recuperado" "\`$MIRROR\` volvió a funcionar correctamente" 3066993
fi
echo $CURRENT_STATE > $STATE_FILE
log "Estado final: $CURRENT_STATE"
rm -rf $TMPDIR
Resultados
Esto nos deja un mirror con una copia de todo Arch Linux, 100% funcional y utilizable a traves de HTTP/HTTPS. Una vez que te aprueban oficialmente en Arch tu mirror aparecerá en la lista de disponibles para tu país y podrá ser rateado por la herramineta reflector. Como puede observarse, hice una clasificación de los mirrors de Aregntina, Brasil y Chile:
17:17 juan ~ ❯ reflector --country Argentina,Chile,Brazil --age 12 --protocol http --protocol https --sort rate --verbose
[2026-06-17 17:18:34] INFO: rating 15 mirror(s) by download speed
[2026-06-17 17:18:34] INFO: Server Rate Time
[2026-06-17 17:18:34] INFO: http://archlinux.c3sl.ufpr.br/ 18725.09 KiB/s 0.45 s
[2026-06-17 17:18:35] INFO: https://archlinux.c3sl.ufpr.br/ 15468.44 KiB/s 0.55 s
[2026-06-17 17:18:35] INFO: http://br.mirrors.cicku.me/archlinux/ 29214.14 KiB/s 0.29 s
[2026-06-17 17:18:36] INFO: https://br.mirrors.cicku.me/archlinux/ 29849.56 KiB/s 0.28 s
[2026-06-17 17:18:36] INFO: http://mirror.ufscar.br/archlinux/ 18610.91 KiB/s 0.45 s
[2026-06-17 17:18:37] INFO: https://mirror.ufscar.br/archlinux/ 14398.79 KiB/s 0.59 s
[2026-06-17 17:18:38] INFO: http://mirror.anquan.cl/archlinux/ 9694.44 KiB/s 0.87 s
[2026-06-17 17:18:39] INFO: https://mirror.anquan.cl/archlinux/ 9619.29 KiB/s 0.88 s
[2026-06-17 17:18:39] INFO: http://mirror.hnd.cl/archlinux/ 21537.76 KiB/s 0.39 s
[2026-06-17 17:18:40] INFO: https://mirror.hnd.cl/archlinux/ 24368.97 KiB/s 0.35 s
[2026-06-17 17:18:40] INFO: http://elmirror.cl/archlinux/ 23256.55 KiB/s 0.36 s
[2026-06-17 17:18:41] INFO: https://elmirror.cl/archlinux/ 16708.61 KiB/s 0.51 s
[2026-06-17 17:18:42] INFO: http://mirrors.ic.unicamp.br/archlinux/ 18047.63 KiB/s 0.47 s
[2026-06-17 17:18:42] INFO: https://mirrors.ic.unicamp.br/archlinux/ 18228.03 KiB/s 0.46 s
[2026-06-17 17:18:42] INFO: https://archlinux.juancho.com.ar/ 46606.56 KiB/s 0.18 s
################################################################################
################# Arch Linux mirrorlist generated by Reflector #################
################################################################################
# With: reflector --country Argentina,Chile,Brazil --age 12 --protocol http --protocol https --sort rate --verbose
# When: 2026-06-17 20:18:42 UTC
# From: https://archlinux.org/mirrors/status/json/
# Retrieved: 2026-06-17 20:14:19 UTC
# Last Check: 2026-06-17 19:56:04 UTC
Server = https://archlinux.juancho.com.ar/$repo/os/$arch
Server = https://br.mirrors.cicku.me/archlinux/$repo/os/$arch
Server = http://br.mirrors.cicku.me/archlinux/$repo/os/$arch
Server = https://mirror.hnd.cl/archlinux/$repo/os/$arch
Server = http://elmirror.cl/archlinux/$repo/os/$arch
Server = http://mirror.hnd.cl/archlinux/$repo/os/$arch
Server = http://archlinux.c3sl.ufpr.br/$repo/os/$arch
Server = http://mirror.ufscar.br/archlinux/$repo/os/$arch
Server = https://mirrors.ic.unicamp.br/archlinux/$repo/os/$arch
Server = http://mirrors.ic.unicamp.br/archlinux/$repo/os/$arch
Server = https://elmirror.cl/archlinux/$repo/os/$arch
Server = https://archlinux.c3sl.ufpr.br/$repo/os/$arch
Server = https://mirror.ufscar.br/archlinux/$repo/os/$arch
Server = http://mirror.anquan.cl/archlinux/$repo/os/$arch
Server = https://mirror.anquan.cl/archlinux/$repo/os/$arch
Además, nuestro mirror aparecerá en la página oficial de Arch:
Te sirvio?
Como Contribuir:
