Nuestra postura, tal y como hemos declarado: somos Geonode y vendemos proxies, por lo que lo más honesto que hay que señalar sobre la descarga de archivos a través de uno de ellos es que el ancho de banda es el único coste y los archivos son grandes. Enrutar una descarga de 4 GB a través de una conexión residencial con tarifa por consumo a 0,79 $/GB supone aproximadamente 3,16 $ por un solo archivo, frente a los 0,56 $ que costaría con el tráfico de un centro de datos a 0,14 $/GB. Si tu descarga no tiene por qué parecer que proviene de una conexión de consumo —y la mayoría de las descargas no lo hacen—, el ancho de banda residencial es tirar el dinero. Los precios proceden de nuestra página de tarifas, consultada en septiembre de 2026. Mejor aún: si puedes descargar directamente, hazlo y no pagues nada.
Dicho esto, a continuación te explicamos cómo hacerlo correctamente.
Las tres formas de nombrar el archivo de salida
Por defecto, curl escribe en la salida estándar, por lo que un comando ingenuo como ``curl https://example.com/file.zip`
` llena la terminal de datos binarios. Hay tres opciones para cambiar esto.
** ``-o filename`
** escribe en un archivo con el nombre que elijas:
curl -o archive.zip https://example.com/download?id=1234
`. Utiliza esta opción siempre que sepas cómo quieres llamar al archivo y, sobre todo, cuando la URL no contenga ningún nombre de archivo útil.
**-O
** utiliza el nombre remoto. El manual de curl lo describe así: «Escribe la salida en un archivo con el mismo nombre que el archivo remoto. Solo se utiliza la parte del nombre del archivo remoto; la ruta se omite».
curl -O https://example.com/files/report.pdf
# saves as report.pdf
Ten en cuenta la salvedad de esa frase. La ruta se descarta, por lo que dos archivos con el mismo nombre base en directorios diferentes se sobrescriben entre sí. Y si la URL termina en una barra o no tiene ningún componente de nombre de archivo, -O
falla.
**-J
** toma el nombre del encabezado Content-Disposition
del servidor. Según la documentación, indica a -O
«que utilice el nombre de archivo de Content-Disposition especificado por el servidor en lugar de extraerlo de la URL». Resulta útil para puntos finales de descarga en los que la URL es un identificador opaco.
Además, conlleva la advertencia más contundente del manual, y merece la pena reproducirla:
ADVERTENCIA: El nombre de archivo de Content-Disposition no se valida ni se depura. Puede contener secuencias de recorrido de rutas (..); no guardes la salida en una ubicación no fiable ni con esta opción activa sin previo aviso.
En otras palabras, «-OJ
» permite que un servidor remoto elija una ruta en tu sistema de archivos. Si se trata de una fuente que controlas, no hay problema. En un script que extrae datos de URL arbitrarias, supone una vulnerabilidad. Si necesitas nombres proporcionados por el servidor procedentes de fuentes no fiables, obtén los encabezados, depura el nombre tú mismo y utiliza «-o
».
Redirecciones: por qué casi siempre es necesario utilizar «-L»
Por defecto, curl no sigue las redirecciones. En -L se indica que «sigue las redirecciones HTTP y repite las solicitudes con el método especificado originalmente».
Esta es la razón más habitual por la que una descarga genera un archivo minúsculo que contiene HTML en lugar del contenido deseado. Las URL de descarga redirigen constantemente: a CDN, a URL firmadas, a servidores espejo o a puntos finales regionales. Sin -L, se guarda la página de redirección.
curl -L -O https://example.com/latest.tar.gz
Dos puntos relacionados.
El orden importa con -o. Al seguir redirecciones con varias URL, curl hace coincidir los argumentos de -o con las URL según su posición. Con una sola URL esto no supone ningún problema, pero puede resultar sorprendente para quienes escriben comandos con varias URL.
Limita el número de redireccionamientos. --max-redirs limita el número de redireccionamientos que seguirá curl. El valor por defecto es generoso, y un bucle de redireccionamientos sin límite no es un modo de fallo deseable en una tarea cron.
El fallo silencioso: guardar las páginas de error como archivos
Esto es lo más importante de este artículo.
Por defecto, curl interpreta una respuesta de error HTTP como una transferencia correcta. Un error 404 tiene un cuerpo; curl descarga el cuerpo; curl termina con el código de salida 0. Ahora tienes un archivo llamado installer.dmg
que contiene una página HTML que dice «No encontrado», y tu script ha continuado como si todo hubiera funcionado correctamente.
--fail
lo soluciona. Según el manual: «Devuelve el código de error 22 para respuestas HTTP con códigos de estado 400 o superiores, sin salida de cuerpo de respuesta».
curl --fail -L -O https://example.com/installer.dmg
Ahora, un 404 genera el código de salida 22 y no se genera ningún archivo. Tu script puede comprobarlo.
Dos mejoras:
**--fail-with-body
** hace lo mismo, pero conserva el cuerpo de la respuesta, lo cual resulta útil cuando las API devuelven mensajes de error JSON que deseas registrar.
**--fail
no es perfecto.** No detecta que un servidor devuelva un 200 con una página de error, algo que hacen algunos. Para cualquier cosa importante, verifica el resultado: comprueba que el tamaño del archivo sea plausible, comprueba la suma de comprobación si se ha publicado alguna, o comprueba los bytes mágicos:
curl --fail -L -o pkg.tar.gz "$URL" || exit 1
file pkg.tar.gz | grep -q gzip || { echo "not a gzip archive"; exit 1; }
Por lo tanto, la línea de descarga canónica, para scripts, es:
curl --fail --silent --show-error --location -o output.bin "$URL"
-sS
suprime el indicador de progreso al tiempo que conserva los mensajes de error, que es lo que se busca en la automatización. Para uso interactivo, -#
te ofrece una barra de progreso sencilla en lugar del indicador predeterminado.
Reanudación de descargas interrumpidas
Los archivos de gran tamaño y las conexiones poco fiables hacen que esto sea esencial. La opción `
`-C -`` se describe así: «Reanuda una transferencia anterior a partir del desplazamiento de bytes indicado. Utiliza '-C -' para indicar a curl que averigüe automáticamente dónde y cómo reanudar la transferencia».
curl -C - -L -O https://example.com/large-file.iso
curl comprueba el tamaño del archivo local y solicita solo el resto utilizando un encabezado «Range». En combinación con los reintentos, esto hace que las descargas largas sean más llevaderas:
curl --fail -L -C - --retry 5 --retry-delay 5 -O https://example.com/large-file.iso
Tres advertencias.
El servidor debe admitir solicitudes de rango. Si no es así, curl no podrá reanudar la descarga y, o bien la reiniciará, o bien fallará. Comprueba si aparece «Accept-Ranges: bytes» en los encabezados de la respuesta.
Reanudar un archivo dañado da como resultado un archivo dañado aún más largo. -C - confía en los bytes locales. Si el archivo parcial se truncó a mitad de la escritura o la fuente cambió, el resultado es erróneo sin que se avise. Verifícalo con una suma de comprobación cuando la fuente publique una.
Un origen modificado invalida la reanudación. Si el archivo se ha sustituido entre intentos, se obtiene una mezcla de ambas versiones sin que se produzca ningún error.
Descarga de muchos archivos en paralelo
Desde la versión 7.66 de curl, la opción «-Z
» realiza «transferencias en paralelo en lugar de secuencialmente», y la opción «--parallel-max
» establece el «número máximo de transferencias que se pueden realizar en paralelo».
curl -Z --parallel-max 8 --fail -L \
-O https://example.com/a.zip \
-O https://example.com/b.zip \
-O https://example.com/c.zip
A partir de un archivo de URL:
xargs -a urls.txt curl -Z --parallel-max 8 --fail -L --remote-name-all
--remote-name-all
aplica el comportamiento de «-O
» a cada URL, lo que evita tener que repetir la opción.
Elige el paralelismo de forma deliberada. A partir de cierto punto, más no significa mejor: los servidores limitan el caudal, tu propia conexión se satura y, más allá de la meseta, generas errores en lugar de rendimiento. Ocho es una cifra inicial razonable para un servidor bien equipado; frente a un servidor pequeño, cuatro o menos es más respetuoso y, a menudo, más rápido en general. El comportamiento aquí es la misma curva de rendimiento que describimos en concurrencia frente a paralelismo: sube, se estabiliza y luego desciende.
Directorios, marcas de tiempo y límites de velocidad
Tres opciones que eliminan la necesidad de escribir scripts envolventes, algo muy habitual.
**--output-dir
** — «Especifica el directorio en el que se guardarán los archivos. Esta opción funciona con las opciones --remote-name o --output». Ya no hace falta escribir cd
antes ni después.
**--create-dirs
** — junto con -o
, «curl crea la jerarquía de directorios locales necesaria. Los directorios creados utilizan el modo 0750 en sistemas Unix». Fíjate en el modo: 0750, no 0755. Si otro usuario o servicio necesita leer esos directorios, esto te sorprenderá.
curl --fail -L --create-dirs -o data/2026/09/report.pdf https://example.com/report.pdf
**--remote-time
** — «Establece la fecha y la hora de modificación del archivo del sistema para que coincidan con la marca de tiempo del archivo remoto». Realmente útil para la creación de réplicas, ya que permite que las herramientas posteriores evalúen la actualidad de los datos.
**--limit-rate
** — «Limita la velocidad de transferencia a la tasa especificada», aceptando los sufijos k
, M
y G
:
curl --limit-rate 2M -L -O https://example.com/large.iso
Merece la pena utilizarlo más de lo que se hace. Saturar tu enlace de subida hace que todo lo demás en la red quede inutilizable, y en una conexión compartida o con límite de datos, establecer un límite de velocidad es una simple cuestión de cortesía. También reduce la probabilidad de que un servidor te considere un usuario abusivo.
Reuniendo las opciones más prácticas:
curl --fail --location --continue-at - --retry 5 \
--remote-time --create-dirs \
--output downloads/archive.tar.gz \
"$URL"
Descargas a través de un proxy
Añade -x
y todo lo anterior sigue siendo válido, con tres modificaciones.
curl -x http://user:pass@proxy.example.com:9000 \
--fail -L -O https://example.com/file.zip
Hay que replantearse los tiempos de espera. --max-time
no es la herramienta adecuada para descargas de tamaño impredecible, ya que una transferencia legítima de gran volumen superará cualquier límite fijo. Utiliza en su lugar el detector de estancamiento basado en la velocidad:
curl -x "$PROXY" --fail -L \
--connect-timeout 10 --speed-limit 1000 --speed-time 30 \
-O https://example.com/large.iso
Este detiene la descarga si el rendimiento se mantiene por debajo de los 1000 bytes por segundo durante 30 segundos, mientras que no interfiere en una descarga lenta pero que avanza a lo largo de varias horas. Hemos tratado el conjunto completo de opciones de tiempo de espera en cómo configurar un tiempo de espera con curl.
El comportamiento de reanudación interactúa con la rotación. Si tu proxy rota las direcciones de salida por conexión, una transferencia reanudada llega desde una dirección diferente a la original. Algunos servidores lo aceptan; otros sirven un espejo diferente; otros rechazan la solicitud de rango. Para descargas largas, utiliza una sesión que mantenga la misma dirección de salida.
El ancho de banda se factura en ambos sentidos en tu cabeza, pero solo una vez en la realidad. Pagas por cada byte que atraviesa el proxy. Si a eso le sumas --retry
y un fallo al 90 % de un archivo grande, la cuenta se complica rápidamente. Utiliza siempre -C -
con las descargas a través de proxy para que, en caso de error, la descarga se reanude en lugar de volver a empezar desde el principio.
Comprobar lo que realmente has descargado
Un código de salida de cero significa que la transferencia se ha completado. No significa que hayas recibido los bytes correctos, y para cualquier cosa que vayas a ejecutar, instalar o archivar, vale la pena subsanar esa discrepancia.
Comprueba que el tamaño sea plausible. Es la comprobación más sencilla posible y detecta truncamientos, páginas de error y respuestas vacías:
SIZE=$(stat -c%s pkg.tar.gz 2>/dev/null || stat -f%z pkg.tar.gz)
[ "$SIZE" -gt 100000 ] || { echo "suspiciously small: $SIZE bytes"; exit 1; }
Comprueba el tipo de archivo. Una página de error HTML guardada con la extensión .tar.gz
pasa desapercibida en una comprobación de tamaño si la página es lo suficientemente grande, pero resulta evidente para file
:
file pkg.tar.gz | grep -q 'gzip compressed' || exit 1
Comprueba una suma de comprobación publicada. Cuando la fuente publique una, esta es la única comprobación que verifica el contenido en lugar de la forma:
curl --fail -sL -O https://example.com/pkg.tar.gz
curl --fail -sL -O https://example.com/pkg.tar.gz.sha256
sha256sum -c pkg.tar.gz.sha256 || exit 1
Ten en cuenta la limitación, ya que a menudo se malinterpreta: obtener la suma de comprobación del mismo servidor a través de la misma conexión te protege contra la corrupción y el truncamiento, pero no contra una fuente comprometida. Si el servidor está sirviendo un archivo defectuoso, proporcionará una suma de comprobación defectuosa que coincida con él. La verificación de la firma con GPG es lo que resuelve este problema, y merece la pena dar ese paso adicional para cualquier cosa que se ejecute con privilegios.
**Compáralo con Content-Length
cuando esté disponible.** curl devuelve un valor distinto de cero con el error 18 cuando una transferencia finaliza antes de la longitud anunciada, lo que detecta automáticamente un tipo de truncamiento, pero solo cuando el servidor ha anunciado una longitud, cosa que no hacen las respuestas fragmentadas.
En el caso de las descargas a través de un proxy, es mejor verificar más que menos. Un salto adicional supone un punto más en el que la transferencia puede verse interrumpida o, en el caso de un intermediario que aplique filtros, alterada. Las comprobaciones anteriores solo suponen unos milisegundos y eliminan toda una categoría de informes de errores confusos.
Cuándo «curl» no es la herramienta adecuada
«curl» es excelente para recuperar una URL. Para otras tareas relacionadas, existen herramientas más adecuadas.
Descargas recursivas y creación de réplicas. curl recupera las URL que le indiques. No rastrea. Para crear una réplica de un directorio o un sitio web, wget -r o una herramienta específica para réplicas es la opción adecuada —una comparación que analizamos en curl vs wget.
Archivos muy grandes a través de enlaces poco fiables. Los gestores de descargas diseñados específicamente para este fin gestionan mejor que curl las transferencias segmentadas y la reanudación agresiva. curl con -C - y reintentos es adecuado; las herramientas especializadas son mejores.
Torrents, rsync, S3 y similares. Son protocolos diferentes con sus propios clientes que gestionan de forma nativa la integridad, la deduplicación y los permisos. aws s3 cp no es simplemente «curl» con pasos adicionales.
Cuando existe un gestor de paquetes. curl | sh para la instalación de software resulta cómodo, pero otorga a un servidor remoto permiso de ejecución arbitraria en tu equipo sin verificación alguna. Utiliza el gestor de paquetes cuando exista uno.
Descargas repetidas del mismo recurso. Si recuperas un archivo de forma periódica para comprobar si hay cambios, las solicitudes condicionales con -z o If-None-Match evitan volver a descargar contenido que no ha cambiado. Una caché es mejor que una descarga rápida.
Preguntas frecuentes
¿Cómo descargo un archivo con curl?
curl -O https://example.com/file.zip lo guarda con el nombre remoto, y -o name te permite elegir el nombre. En la práctica, añade --fail y -L: sin ellos, curl no seguirá ninguna redirección y guardará las páginas de error como si fueran el archivo que has solicitado.
¿Por qué curl descarga un archivo vacío o un archivo HTML?
Casi siempre se debe a una redirección que no has seguido —añade -L— o a un error HTTP guardado como archivo, lo cual se evita con --fail. Comprueba lo que has obtenido con file downloaded.zip o abriéndolo en un editor de texto; una página de error HTML se reconoce de inmediato.
¿Cómo reanudo una descarga interrumpida con curl?
curl -C - -O <url>. curl comprueba el tamaño del archivo local y solicita el resto mediante una solicitud de rango. El servidor debe admitir rangos —busca Accept-Ranges: bytes— y ten en cuenta que reanudar un archivo parcial dañado produce un archivo dañado más largo sin previo aviso.
¿Cómo descargo varios archivos con curl?
Utiliza -Z para transferencias paralelas con --parallel-max para limitar la concurrencia, y --remote-name-all para que cada URL se comporte como un nombre remoto. Para una lista en un archivo, canalízala a través de xargs. Mantén un paralelismo moderado: el rendimiento se estabiliza y luego cae a medida que los servidores comienzan a limitar la velocidad.
¿Cómo limito la velocidad de descarga en curl?
--limit-rate 2M limita la transferencia a 2 megabytes por segundo, y la opción admite los sufijos k, M y G. Vale la pena utilizarla en conexiones compartidas y con servidores pequeños, tanto para mantener el resto de tu red utilizable como para evitar que te traten como un usuario abusivo.
¿Cuál es la diferencia entre -O y -o en curl?
-O utiliza el nombre de archivo de la URL, descartando la ruta. -o escribe en un nombre que tú especifiques. Utiliza -o cuando la URL no tenga un nombre de archivo válido, cuando necesites un nombre específico o cuando quieras evitar conflictos entre archivos con el mismo nombre procedentes de rutas diferentes.
¿Es seguro «curl -OJ»?
No con fuentes no fiables. El manual advierte explícitamente que el nombre de archivo «Content-Disposition» «no se valida ni se depura» y «puede contener secuencias de recorrido de rutas». Eso significa que un servidor remoto elige dónde se guarda el archivo. Obtén el encabezado, depura el nombre tú mismo y utiliza «-o».
¿Cómo compruebo si una descarga con curl se ha realizado correctamente?
Utiliza --fail para que los errores HTTP generen el código de salida 22 en lugar de mostrar una página de error, y luego comprueba el código de salida. Para cualquier cosa importante, verifica más a fondo: comprueba que el tamaño del archivo sea plausible, ejecuta file para confirmar el tipo y compara la suma de comprobación si la fuente la publica.
Conclusión
La llamada simple a curl -O que aparece en la mayoría de los ejemplos funciona hasta que deja de hacerlo, y su modo de fallo es el peor posible: un código de salida de cero y un archivo que contiene algo distinto de lo que se solicitó.
Hay dos opciones que solucionan esto. -L sigue las redirecciones que utilizan prácticamente todas las URL de descarga reales, y --fail impide que curl guarde las respuestas de error HTTP como si fueran contenido. Añade -C - para cualquier archivo de gran tamaño, ya que una descarga reanudable marca la diferencia entre un problema de red pasajero y tener que empezar de nuevo.
Para los scripts, curl --fail --silent --show-error --location es la línea que vale la pena memorizar, junto con --continue-at - y --retry cuando los archivos son grandes. Y sean cuales sean los parámetros, verifica el resultado en lugar de fiarte únicamente del código de salida: comprueba el tamaño, comprueba el tipo y comprueba la suma de comprobación si existe. Las descargas fallan de forma silenciosa con más frecuencia de lo que fallan de forma evidente.